diff options
author | kevers <kevers@chromium.org> | 2014-10-27 07:34:45 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-10-27 14:35:14 +0000 |
commit | 9d9b00dc7bc6a38448ff0d53738d311fa5df4df4 (patch) | |
tree | b348f60d6bc4de9261c2704cfe84338d9094e268 /third_party/google_input_tools | |
parent | 48e356db5b69123ce7178a51778feef47ee687c0 (diff) | |
download | chromium_src-9d9b00dc7bc6a38448ff0d53738d311fa5df4df4.zip chromium_src-9d9b00dc7bc6a38448ff0d53738d311fa5df4df4.tar.gz chromium_src-9d9b00dc7bc6a38448ff0d53738d311fa5df4df4.tar.bz2 |
Add third_party/google-input-tools: Take 2
BUG=401729
Abandoning Issue 451873002 in favor of directly adding google-input-tools to src/third_party. A subset of the open sourced library depends on third_party code where the licensing is unclear. The required pieces strictly support Apache 2.0 licensing.
Review URL: https://codereview.chromium.org/674153004
Cr-Commit-Position: refs/heads/master@{#301361}
Diffstat (limited to 'third_party/google_input_tools')
198 files changed, 59485 insertions, 0 deletions
diff --git a/third_party/google_input_tools/LICENSE b/third_party/google_input_tools/LICENSE new file mode 100644 index 0000000..806b547 --- /dev/null +++ b/third_party/google_input_tools/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2013 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.
\ No newline at end of file diff --git a/third_party/google_input_tools/Makefile b/third_party/google_input_tools/Makefile new file mode 100644 index 0000000..a648436 --- /dev/null +++ b/third_party/google_input_tools/Makefile @@ -0,0 +1,24 @@ +# 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"); +# +SHELL:=/bin/bash +BUILD_DIR:=src/chrome/os/inputview/build + +all: inputview.js + +inputview.js: clean + @make -C $(BUILD_DIR) + @cp $(BUILD_DIR)/inputview.js . + +clean: + $(RM) $(BUILD_DIR)/inputview.js diff --git a/third_party/google_input_tools/OWNERS b/third_party/google_input_tools/OWNERS new file mode 100644 index 0000000..aaea42c --- /dev/null +++ b/third_party/google_input_tools/OWNERS @@ -0,0 +1,4 @@ +bshe@chromium.org +kevers@chromium.org +rsadam@chromium.org +shuchen@chromium.org diff --git a/third_party/google_input_tools/README.chromium b/third_party/google_input_tools/README.chromium new file mode 100644 index 0000000..e5f351b --- /dev/null +++ b/third_party/google_input_tools/README.chromium @@ -0,0 +1,22 @@ +Name: Google Input Tools +Short Name: google_input_tools +URL: https://github.com/googlei18n/google-input-tools.git +Version: 1.0.4.0 +Revision: @b99e802e53613e07f24a779695c1c6f3bd2f864a +License: Apache 2.0 +License File: LICENSE +Security Critical: yes + +Description: +This directory contains source for the google-input-tools project, which +provides multi-lingual input support. In particular, google-input-tools is +used in ChromeOS to provide a fallback virtual keyboard for IMEs that are not +VK-aware. + +To update to a newer version of google-input-tools, run the following script: + +update.py --input=_path_to_google_input_tools_ --lib=_path_to_closure_lib_ + +Local Modifications: +Only includes the portion of google-input-tools required to build an inputview- +based virtual keyboard.
\ No newline at end of file diff --git a/third_party/google_input_tools/src/chrome/os/datasource.js b/third_party/google_input_tools/src/chrome/os/datasource.js new file mode 100644 index 0000000..5c8f14e --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/datasource.js @@ -0,0 +1,189 @@ +// 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.DataSource'); + +goog.require('goog.events'); +goog.require('goog.events.Event'); +goog.require('goog.events.EventTarget'); +goog.require('goog.functions'); + + +/** + * The data source. + * + * @param {number} numOfCanddiate The number of canddiate to fetch. + * @param {function(string, !Array.<!Object>)} callback . + * @constructor + * @extends {goog.events.EventTarget} + */ +i18n.input.chrome.DataSource = function(numOfCanddiate, callback) { + goog.base(this); + + /** + * The number of candidates to fetch. + * + * @type {number} + */ + this.numOfCandidate = numOfCanddiate; + + /** @protected {function(string, !Array.<!Object>)} */ + this.callback = callback; +}; +goog.inherits(i18n.input.chrome.DataSource, goog.events.EventTarget); + + +/** @type {boolean} */ +i18n.input.chrome.DataSource.prototype.ready = false; + + +/** + * The correction level. + * + * @protected {number} + */ +i18n.input.chrome.DataSource.prototype.correctionLevel = 0; + + +/** + * The language code. + * + * @type {string} + * @protected + */ +i18n.input.chrome.DataSource.prototype.language; + + +/** + * Sets the langauge code. + * + * @param {string} language The language code. + */ +i18n.input.chrome.DataSource.prototype.setLanguage = function( + language) { + this.language = language; +}; + + +/** + * True if the datasource is ready. + * + * @return {boolean} . + */ +i18n.input.chrome.DataSource.prototype.isReady = function() { + return this.ready; +}; + + +/** + * Creates the common payload for completion or prediction request. + * + * @return {!Object} The payload. + * @protected + */ +i18n.input.chrome.DataSource.prototype.createCommonPayload = + function() { + return { + 'itc': this.getInputToolCode(), + 'num': this.numOfCandidate + }; +}; + + +/** + * Gets the input tool code. + * + * @return {string} . + */ +i18n.input.chrome.DataSource.prototype.getInputToolCode = function() { + return this.language + '-t-i0-und'; +}; + + +/** + * Sends completion request. + * + * @param {string} query The query . + * @param {string} context The context . + * @param {!Object=} opt_spatialData . + */ +i18n.input.chrome.DataSource.prototype.sendCompletionRequest = + goog.functions.NULL; + + +/** + * Sends prediciton request. + * + * @param {string} context The context. + */ +i18n.input.chrome.DataSource.prototype.sendPredictionRequest = + goog.functions.NULL; + + +/** + * Sets the correction level. + * + * @param {number} level . + */ +i18n.input.chrome.DataSource.prototype.setCorrectionLevel = function(level) { + this.correctionLevel = level; +}; + + +/** + * Clears the data source. + */ +i18n.input.chrome.DataSource.prototype.clear = goog.functions.NULL; + + +/** + * The event type. + * + * @enum {string} + */ +i18n.input.chrome.DataSource.EventType = { + CANDIDATES_BACK: goog.events.getUniqueId('cb'), + READY: goog.events.getUniqueId('r') +}; + + + +/** + * The candidates is fetched back. + * + * @param {string} source The source. + * @param {!Array.<!Object>} candidates The candidates. + * @constructor + * @extends {goog.events.Event} + */ +i18n.input.chrome.DataSource.CandidatesBackEvent = + function(source, candidates) { + goog.base(this, i18n.input.chrome.DataSource.EventType.CANDIDATES_BACK); + + /** + * The source. + * + * @type {string} + */ + this.source = source; + + /** + * The candidate list. + * + * @type {!Array.<!Object>} + */ + this.candidates = candidates; +}; +goog.inherits(i18n.input.chrome.DataSource.CandidatesBackEvent, + goog.events.Event); + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/adapter.js b/third_party/google_input_tools/src/chrome/os/inputview/adapter.js new file mode 100644 index 0000000..dd7335b --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/adapter.js @@ -0,0 +1,667 @@ +// 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.Adapter'); + +goog.require('goog.events.Event'); +goog.require('goog.events.EventHandler'); +goog.require('goog.events.EventTarget'); +goog.require('goog.events.EventType'); +goog.require('goog.object'); +goog.require('i18n.input.chrome.DataSource'); +goog.require('i18n.input.chrome.inputview.ReadyState'); +goog.require('i18n.input.chrome.inputview.StateType'); +goog.require('i18n.input.chrome.inputview.events.EventType'); +goog.require('i18n.input.chrome.inputview.events.SurroundingTextChangedEvent'); +goog.require('i18n.input.chrome.message.ContextType'); +goog.require('i18n.input.chrome.message.Event'); +goog.require('i18n.input.chrome.message.Name'); +goog.require('i18n.input.chrome.message.Type'); + +goog.scope(function() { +var CandidatesBackEvent = i18n.input.chrome.DataSource.CandidatesBackEvent; +var ContextType = i18n.input.chrome.message.ContextType; +var Type = i18n.input.chrome.message.Type; +var Name = i18n.input.chrome.message.Name; + + + +/** + * The adapter for interview. + * + * @param {!i18n.input.chrome.inputview.ReadyState} readyState . + * @extends {goog.events.EventTarget} + * @constructor + */ +i18n.input.chrome.inputview.Adapter = function(readyState) { + goog.base(this); + + /** + * Whether the keyboard is visible. + * + * @type {boolean} + */ + this.isVisible = !document.webkitHidden; + + /** + * The modifier state map. + * + * @type {!Object.<i18n.input.chrome.inputview.StateType, boolean>} + * @private + */ + this.modifierState_ = {}; + + /** + * The system ready state. + * + * @private {!i18n.input.chrome.inputview.ReadyState} + */ + this.readyState_ = readyState; + + chrome.runtime.onMessage.addListener(this.onMessage_.bind(this)); + + /** @private {!goog.events.EventHandler} */ + this.handler_ = new goog.events.EventHandler(this); + this.handler_.listen(document, 'webkitvisibilitychange', + this.onVisibilityChange_); +}; +goog.inherits(i18n.input.chrome.inputview.Adapter, + goog.events.EventTarget); +var Adapter = i18n.input.chrome.inputview.Adapter; + + +/** @type {boolean} */ +Adapter.prototype.isA11yMode = false; + + +/** @type {boolean} */ +Adapter.prototype.isExperimental = false; + + +/** @type {boolean} */ +Adapter.prototype.showGlobeKey = false; + + +/** @protected {string} */ +Adapter.prototype.contextType = ContextType.DEFAULT; + + +/** @type {string} */ +Adapter.prototype.screen = ''; + + +/** @type {boolean} */ +Adapter.prototype.isChromeVoxOn = false; + + +/** @type {string} */ +Adapter.prototype.textBeforeCursor = ''; + + +/** + * Callback for updating settings. + * + * @param {!Object} message . + * @private + */ +Adapter.prototype.onUpdateSettings_ = function(message) { + this.contextType = message['contextType']; + this.screen = message['screen']; + this.dispatchEvent(new i18n.input.chrome.message.Event(Type.UPDATE_SETTINGS, + message)); +}; + + +/** + * Sets the modifier states. + * + * @param {i18n.input.chrome.inputview.StateType} stateType . + * @param {boolean} enable True to enable the state, false otherwise. + */ +Adapter.prototype.setModifierState = function(stateType, enable) { + this.modifierState_[stateType] = enable; +}; + + +/** + * Clears the modifier states. + */ +Adapter.prototype.clearModifierStates = function() { + this.modifierState_ = {}; +}; + + +/** + * Simulates to send 'keydown' and 'keyup' event. + * + * @param {string} key + * @param {string} code + * @param {number=} opt_keyCode The key code. + * @param {!Object=} opt_spatialData . + */ +Adapter.prototype.sendKeyDownAndUpEvent = function(key, code, opt_keyCode, + opt_spatialData) { + this.sendKeyEvent_([ + this.generateKeyboardEvent_( + goog.events.EventType.KEYDOWN, key, code, opt_keyCode, opt_spatialData), + this.generateKeyboardEvent_( + goog.events.EventType.KEYUP, key, code, opt_keyCode, opt_spatialData) + ]); +}; + + +/** + * Simulates to send 'keydown' event. + * + * @param {string} key + * @param {string} code + * @param {number=} opt_keyCode The key code. + * @param {!Object=} opt_spatialData . + */ +Adapter.prototype.sendKeyDownEvent = function(key, code, opt_keyCode, + opt_spatialData) { + this.sendKeyEvent_([this.generateKeyboardEvent_( + goog.events.EventType.KEYDOWN, key, code, opt_keyCode, + opt_spatialData)]); +}; + + +/** + * Simulates to send 'keyup' event. + * + * @param {string} key + * @param {string} code + * @param {number=} opt_keyCode The key code. + * @param {!Object=} opt_spatialData . + */ +Adapter.prototype.sendKeyUpEvent = function(key, code, opt_keyCode, + opt_spatialData) { + this.sendKeyEvent_([this.generateKeyboardEvent_( + goog.events.EventType.KEYUP, key, code, opt_keyCode, opt_spatialData)]); +}; + + +/** + * Use {@code chrome.input.ime.sendKeyEvents} to simulate key events. + * + * @param {!Array.<!Object.<string, string|boolean>>} keyData . + * @private + */ +Adapter.prototype.sendKeyEvent_ = function(keyData) { + chrome.runtime.sendMessage( + goog.object.create(Name.MSG_TYPE, Type.SEND_KEY_EVENT, Name.KEY_DATA, + keyData)); +}; + + +/** + * Generates a {@code ChromeKeyboardEvent} by given values. + * + * @param {string} type . + * @param {string} key The key. + * @param {string} code The code. + * @param {number=} opt_keyCode The key code. + * @param {!Object=} opt_spatialData . + * @return {!Object.<string, string|boolean>} + * @private + */ +Adapter.prototype.generateKeyboardEvent_ = function( + type, key, code, opt_keyCode, opt_spatialData) { + var StateType = i18n.input.chrome.inputview.StateType; + var ctrl = !!this.modifierState_[StateType.CTRL]; + var alt = !!this.modifierState_[StateType.ALT]; + + if (ctrl || alt) { + key = ''; + } + var result = { + 'type': type, + 'key': key, + 'code': code, + 'keyCode': opt_keyCode || 0, + 'spatialData': opt_spatialData + }; + + result['altKey'] = alt; + result['ctrlKey'] = ctrl; + result['shiftKey'] = !!this.modifierState_[StateType.SHIFT]; + result['capsLock'] = !!this.modifierState_[StateType.CAPSLOCK]; + + return result; +}; + + +/** + * Callback when surrounding text is changed. + * + * @param {string} text . + * @private + */ +Adapter.prototype.onSurroundingTextChanged_ = function(text) { + this.textBeforeCursor = text; + this.dispatchEvent(new i18n.input.chrome.inputview.events. + SurroundingTextChangedEvent(this.textBeforeCursor)); +}; + + +/** + * Gets the context. + * + * @return {string} . + */ +Adapter.prototype.getContext = function() { + var matches = this.textBeforeCursor.match(/([a-zA-Z'-Ḁ-ỹÀ-ȳ]+)\s+$/); + var text = matches ? matches[1] : ''; + return text; +}; + + +/** + * Gets the context type. + * + * @return {string} . + */ +Adapter.prototype.getContextType = function() { + return this.contextType || ContextType.DEFAULT; +}; + + +/** + * Sends request for handwriting. + * + * @param {!Object} payload . + */ +Adapter.prototype.sendHwtRequest = function(payload) { + chrome.runtime.sendMessage(goog.object.create( + Name.MSG_TYPE, Type.HWT_REQUEST, Name.MSG, payload + )); +}; + + +/** + * True if it is a password box. + * + * @return {boolean} . + */ +Adapter.prototype.isPasswordBox = function() { + return this.contextType == 'password'; +}; + + +/** + * Callback when blurs in the context. + * + * @private + */ +Adapter.prototype.onContextBlur_ = function() { + this.contextType = ''; + this.dispatchEvent(new goog.events.Event(i18n.input.chrome.inputview.events. + EventType.CONTEXT_BLUR)); +}; + + +/** + * Callback when focus on a context. + * + * @param {string} contextType . + * @private + */ +Adapter.prototype.onContextFocus_ = function(contextType) { + this.contextType = contextType; + this.dispatchEvent(new goog.events.Event(i18n.input.chrome.inputview.events. + EventType.CONTEXT_FOCUS)); +}; + + +/** + * Intializes the communication to background page. + * + * @param {string} languageCode The language code. + * @private + */ +Adapter.prototype.initBackground_ = function(languageCode) { + chrome.runtime.getBackgroundPage((function() { + chrome.runtime.sendMessage( + goog.object.create(Name.MSG_TYPE, Type.CONNECT)); + chrome.runtime.sendMessage(goog.object.create(Name.MSG_TYPE, + Type.VISIBILITY_CHANGE, Name.VISIBILITY, !document.webkitHidden)); + if (languageCode) { + this.setLanguage(languageCode); + } + }).bind(this)); +}; + + +/** + * Loads the keyboard settings. + * + * @param {string} languageCode The language code. + */ +Adapter.prototype.initialize = function(languageCode) { + if (chrome.accessibilityFeatures && + chrome.accessibilityFeatures.spokenFeedback) { + chrome.accessibilityFeatures.spokenFeedback.get({}, (function(details) { + this.isChromeVoxOn = details['value']; + }).bind(this)); + chrome.accessibilityFeatures.spokenFeedback.onChange.addListener((function( + details) { + this.isChromeVoxOn = details['value']; + }).bind(this)); + } + + this.initBackground_(languageCode); + + var StateType = i18n.input.chrome.inputview.ReadyState.StateType; + if (window.inputview) { + if (inputview.getKeyboardConfig) { + inputview.getKeyboardConfig((function(config) { + this.isA11yMode = !!config['a11ymode']; + this.isExperimental = !!config['experimental']; + this.readyState_.markStateReady(StateType.KEYBOARD_CONFIG_READY); + if (this.readyState_.isReady(StateType.IME_LIST_READY)) { + this.dispatchEvent(new goog.events.Event( + i18n.input.chrome.inputview.events.EventType.SETTINGS_READY)); + } + }).bind(this)); + } else { + this.readyState_.markStateReady(StateType.KEYBOARD_CONFIG_READY); + } + if (inputview.getInputMethods) { + inputview.getInputMethods((function(inputMethods) { + // Only show globe key to switching between IMEs when there are more + // than one IME. + this.showGlobeKey = inputMethods.length > 1; + this.readyState_.markStateReady(StateType.IME_LIST_READY); + if (this.readyState_.isReady(StateType.KEYBOARD_CONFIG_READY)) { + this.dispatchEvent(new goog.events.Event( + i18n.input.chrome.inputview.events.EventType.SETTINGS_READY)); + } + }).bind(this)); + } else { + this.readyState_.markStateReady(StateType.IME_LIST_READY); + } + } else { + this.readyState_.markStateReady(StateType.IME_LIST_READY); + this.readyState_.markStateReady(StateType.KEYBOARD_CONFIG_READY); + } + + if (this.readyState_.isReady(StateType.KEYBOARD_CONFIG_READY) && + this.readyState_.isReady(StateType.IME_LIST_READY)) { + window.setTimeout((function() { + this.dispatchEvent(new goog.events.Event( + i18n.input.chrome.inputview.events.EventType.SETTINGS_READY)); + }).bind(this), 0); + } +}; + + +/** + * Gets the currently activated input method. + * + * @param {function(string)} callback . + */ +Adapter.prototype.getCurrentInputMethod = function(callback) { + if (window.inputview && inputview.getCurrentInputMethod) { + inputview.getCurrentInputMethod(callback); + } else { + callback('DU'); + } +}; + + +/** + * Gets the list of all activated input methods. + * + * @param {function(Array.<Object>)} callback . + */ +Adapter.prototype.getInputMethods = function(callback) { + if (window.inputview && inputview.getInputMethods) { + inputview.getInputMethods(callback); + } else { + // Provides a dummy IME item to enable IME switcher UI. + callback([ + {'indicator': 'DU', 'id': 'DU', 'name': 'Dummy IME', 'command': 1}]); + } +}; + + +/** + * Switches to the input method with id equals |inputMethodId|. + * + * @param {!string} inputMethodId . + */ +Adapter.prototype.switchToInputMethod = function(inputMethodId) { + if (window.inputview && inputview.switchToInputMethod) { + inputview.switchToInputMethod(inputMethodId); + } +}; + + +/** + * Callback for visibility change on the input view window. + * + * @private + */ +Adapter.prototype.onVisibilityChange_ = function() { + this.isVisible = !document.webkitHidden; + this.dispatchEvent(new goog.events.Event(i18n.input.chrome.inputview. + events.EventType.VISIBILITY_CHANGE)); + chrome.runtime.sendMessage(goog.object.create(Name.MSG_TYPE, + Type.VISIBILITY_CHANGE, Name.VISIBILITY, !document.webkitHidden)); +}; + + +/** + * Sends request for completion. + * + * @param {string} query . + * @param {!Object=} opt_spatialData . + */ +Adapter.prototype.sendCompletionRequest = function(query, opt_spatialData) { + var spatialData = {}; + if (opt_spatialData) { + spatialData[Name.SOURCES] = opt_spatialData.sources; + spatialData[Name.POSSIBILITIES] = opt_spatialData.possibilities; + } + chrome.runtime.sendMessage(goog.object.create(Name.MSG_TYPE, + Type.COMPLETION, Name.TEXT, query, Name.SPATIAL_DATA, spatialData)); +}; + + +/** + * Selects the candidate. + * + * @param {!Object} candidate . + */ +Adapter.prototype.selectCandidate = function(candidate) { + chrome.runtime.sendMessage(goog.object.create( + Name.MSG_TYPE, Type.SELECT_CANDIDATE, Name.CANDIDATE, candidate)); +}; + + +/** + * Commits the text. + * + * @param {string} text . + */ +Adapter.prototype.commitText = function(text) { + chrome.runtime.sendMessage(goog.object.create( + Name.MSG_TYPE, Type.COMMIT_TEXT, Name.TEXT, text)); +}; + + +/** + * Sets the language. + * + * @param {string} language . + */ +Adapter.prototype.setLanguage = function(language) { + chrome.runtime.sendMessage(goog.object.create( + Name.MSG_TYPE, Type.SET_LANGUAGE, Name.LANGUAGE, language)); +}; + + +/** + * Callbck when completion is back. + * + * @param {!Object} message . + * @private + */ +Adapter.prototype.onCandidatesBack_ = function(message) { + var source = message['source'] || ''; + var candidates = message['candidates'] || []; + this.dispatchEvent(new CandidatesBackEvent(source, candidates)); +}; + + +/** + * Hides the keyboard. + */ +Adapter.prototype.hideKeyboard = function() { + chrome.input.ime.hideInputView(); +}; + + +/** + * Sends Input Tool code to background. + * + * @param {string} inputToolCode . + */ +Adapter.prototype.setInputToolCode = function(inputToolCode) { + chrome.runtime.sendMessage( + goog.object.create( + Name.MSG_TYPE, + Type.HWT_SET_INPUTTOOL, + Name.MSG, + inputToolCode)); +}; + + +/** + * Sends DOUBLE_CLICK_ON_SPACE_KEY message. + */ +Adapter.prototype.doubleClickOnSpaceKey = function() { + chrome.runtime.sendMessage( + goog.object.create( + Name.MSG_TYPE, + Type.DOUBLE_CLICK_ON_SPACE_KEY)); +}; + + +/** + * Sends message to the background when switch to emoji. + * + */ +Adapter.prototype.setEmojiInputToolCode = function() { + chrome.runtime.sendMessage( + goog.object.create( + Name.MSG_TYPE, + Type.EMOJI_SET_INPUTTOOL)); +}; + + +/** + * Sends message to the background when do internal inputtool switch. + * + * @param {boolean} inputToolValue The value of the language flag. + */ +Adapter.prototype.toggleLanguageState = function(inputToolValue) { + chrome.runtime.sendMessage( + goog.object.create( + Name.MSG_TYPE, + Type.TOGGLE_LANGUAGE_STATE, + Name.MSG, + inputToolValue)); +}; + + +/** + * Sends unset Input Tool code to background. + */ +Adapter.prototype.unsetInputToolCode = function() { + chrome.runtime.sendMessage( + goog.object.create( + Name.MSG_TYPE, + Type.HWT_UNSET_INPUTTOOL)); +}; + + +/** + * Sends message to the background when switch to other mode from emoji. + * + */ +Adapter.prototype.unsetEmojiInputToolCode = function() { + chrome.runtime.sendMessage( + goog.object.create( + Name.MSG_TYPE, + Type.EMOJI_UNSET_INPUTTOOL)); +}; + + +/** + * Processes incoming message from option page or inputview window. + * + * @param {*} request Message from option page or inputview window. + * @param {*} sender Information about the script + * context that sent the message. + * @param {function(*): void} sendResponse Function to call to send a response. + * @return {boolean|undefined} {@code true} to keep the message channel open in + * order to send a response asynchronously. + * @private + */ +Adapter.prototype.onMessage_ = function(request, sender, sendResponse) { + var type = request[Name.MSG_TYPE]; + var msg = request[Name.MSG]; + switch (type) { + case Type.CANDIDATES_BACK: + this.onCandidatesBack_(msg); + break; + case Type.CONTEXT_FOCUS: + this.onContextFocus_(request[Name.CONTEXT_TYPE]); + break; + case Type.CONTEXT_BLUR: + this.onContextBlur_(); + break; + case Type.SURROUNDING_TEXT_CHANGED: + this.onSurroundingTextChanged_(request[Name.TEXT]); + break; + case Type.UPDATE_SETTINGS: + this.onUpdateSettings_(msg); + break; + case Type.HWT_NETWORK_ERROR: + case Type.HWT_PRIVACY_INFO: + this.dispatchEvent(new i18n.input.chrome.message.Event(type, msg)); + break; + } +}; + + +/** + * Sends the privacy confirmed message to background and broadcasts it. + */ +Adapter.prototype.sendHwtPrivacyConfirmMessage = function() { + chrome.runtime.sendMessage( + goog.object.create(Name.MSG_TYPE, Type.HWT_PRIVACY_GOT_IT)); + this.dispatchEvent( + new goog.events.Event(Type.HWT_PRIVACY_GOT_IT)); +}; + + +/** @override */ +Adapter.prototype.disposeInternal = function() { + goog.dispose(this.handler_); + + goog.base(this, 'disposeInternal'); +}; +}); // goog.scope + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/candidatesinfo.js b/third_party/google_input_tools/src/chrome/os/inputview/candidatesinfo.js new file mode 100644 index 0000000..13a071b --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/candidatesinfo.js @@ -0,0 +1,42 @@ +// 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.CandidatesInfo'); + + + +/** + * The candidates information. + * + * @param {string} source . + * @param {!Array.<!Object>} candidates . + * @constructor + */ +i18n.input.chrome.inputview.CandidatesInfo = function(source, candidates) { + /** @type {string} */ + this.source = source; + + /** @type {!Array.<!Object>} */ + this.candidates = candidates; +}; + + +/** + * Gets an empty suggestions instance. + * + * @return {!i18n.input.chrome.inputview.CandidatesInfo} . + */ +i18n.input.chrome.inputview.CandidatesInfo.getEmpty = function() { + return new i18n.input.chrome.inputview.CandidatesInfo('', []); +}; + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/canvas.js b/third_party/google_input_tools/src/chrome/os/inputview/canvas.js new file mode 100644 index 0000000..b417047 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/canvas.js @@ -0,0 +1,425 @@ +// 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"); +// + +/** + * @fileoverview Handwriting input canvas. + * @author har@google.com (Henry A. Rowley) + */ + +goog.provide('i18n.input.hwt.Canvas'); + +goog.require('goog.a11y.aria.Announcer'); +goog.require('goog.a11y.aria.LivePriority'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('goog.events.Event'); +goog.require('goog.events.EventHandler'); +goog.require('goog.events.EventType'); +goog.require('goog.log'); +goog.require('goog.ui.Container'); +goog.require('i18n.input.hwt.EventType'); +goog.require('i18n.input.hwt.StrokeHandler'); +goog.require('i18n.input.hwt.css'); + + + +/** + * The handwriting canvas UI element. + * + * @constructor + * @param {!Document=} opt_topDocument The top document for MOUSEUP event. + * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @param {number=} opt_inkWidth The ink width. + * @param {string=} opt_inkColor The ink color. + * @extends {goog.ui.Container} + */ +i18n.input.hwt.Canvas = function(opt_topDocument, opt_domHelper, + opt_eventTarget, opt_inkWidth, opt_inkColor) { + goog.base(this, undefined, undefined, opt_domHelper); + this.setParentEventTarget(opt_eventTarget || null); + + /** + * The stroke handler. + * + * @type {!i18n.input.hwt.StrokeHandler} + * @private + */ + this.strokeHandler_; + + /** + * The top document. + * + * @type {!Document} + * @private + */ + this.topDocument_ = opt_topDocument || document; + + /** + * Canvas which is the area showing the ink. Note that since we're + * using a canvas here probably this code won't work in IE. + * + * @type {!Element} + * @private + */ + this.writingCanvas_; + + /** + * Context for drawing into the writing canvas. + * + * @type {!CanvasRenderingContext2D} + * @private + */ + this.writingContext_; + + /** + * An array of strokes for passing to the recognizer. + * + * @type {!Array} + */ + this.strokeList = []; + + /** + * The current stroke. + * + * @type {!Array} + * @private + */ + this.stroke_ = []; + + /** + * Starting time of the stroke in milliseconds. + * + * @type {number} + * @private + */ + this.startTime_ = 0; + + /** + * Event handler. + * + * @type {!goog.events.EventHandler} + * @private + */ + this.handler_ = new goog.events.EventHandler(this); + + /** + * The annoucer for screen reader. + * + * @type {!goog.a11y.aria.Announcer} + * @private + */ + this.announcer_ = new goog.a11y.aria.Announcer(this.getDomHelper()); + + /** + * Width of the ink line. + * + * @type {number} + * @private + */ + this.inkWidth_ = opt_inkWidth || 6; + + /** + * Color of the ink before it has been recognized. + * + * @type {string} + * @private + */ + this.inkColor_ = opt_inkColor || '#4D90FE'; +}; +goog.inherits(i18n.input.hwt.Canvas, goog.ui.Container); + + +/** + * The class logger. + * + * @type {goog.log.Logger} + * @private + */ +i18n.input.hwt.Canvas.prototype.logger_ = + goog.log.getLogger('i18n.input.hwt.Canvas'); + + +/** + * @desc Label for handwriting panel used to draw strokes. + */ +i18n.input.hwt.Canvas.MSG_INPUTTOOLS_HWT_PANEL = goog.getMsg('panel'); + + +/** + * @desc The hint on the canvas to indicate users they can draw here. + */ +i18n.input.hwt.Canvas.MSG_HANDWRITING_HINT = goog.getMsg('Draw a symbol here'); + + +/** @override */ +i18n.input.hwt.Canvas.prototype.createDom = function() { + goog.base(this, 'createDom'); + + var dom = this.getDomHelper(); + this.writingCanvas_ = dom.createDom(goog.dom.TagName.CANVAS, + i18n.input.hwt.css.CANVAS); + this.writingCanvas_.width = 425; + this.writingCanvas_.height = 194; + dom.appendChild(this.getElement(), this.writingCanvas_); + this.writingContext_ = this.writingCanvas_.getContext('2d'); +}; + + +/** @override */ +i18n.input.hwt.Canvas.prototype.enterDocument = function() { + goog.base(this, 'enterDocument'); + + this.setFocusable(false); + this.setFocusableChildrenAllowed(false); + // Sets up stroke handler. + this.strokeHandler_ = new i18n.input.hwt.StrokeHandler(this.writingCanvas_, + this.topDocument_); + this.handler_. + listen(this.strokeHandler_, + i18n.input.hwt.StrokeHandler.EventType.STROKE_START, + this.onStrokeStart_). + listen(this.strokeHandler_, + i18n.input.hwt.StrokeHandler.EventType.STROKE, + this.onStroke_). + listen(this.strokeHandler_, + i18n.input.hwt.StrokeHandler.EventType.STROKE_END, + this.onStrokeEnd_). + listen(this.writingCanvas_, goog.events.EventType.MOUSEOVER, + this.onMouseOver_). + listen(this.writingCanvas_, goog.events.EventType.MOUSEDOWN, + goog.events.Event.stopPropagation); +}; + + +/** + * Shows the hint message for the canvas. + */ +i18n.input.hwt.Canvas.prototype.showHint = function() { + this.writingContext_.font = '20px aria,sans-serif'; + this.writingContext_.fontWeight = 'bold'; + this.writingContext_.fillStyle = '#CCC'; + this.writingContext_.fillText(i18n.input.hwt.Canvas.MSG_HANDWRITING_HINT, + 30, 120); +}; + + +/** + * Draw a point with the given color. + * + * @param {string} color Color to draw the point. + * @param {!i18n.input.hwt.StrokeHandler.Point} point The point. + * @private + */ +i18n.input.hwt.Canvas.prototype.drawPoint_ = function(color, point) { + this.writingContext_.beginPath(); + this.writingContext_.strokeStyle = color; + this.writingContext_.fillStyle = color; + this.writingContext_.arc(point.x, point.y, + this.inkWidth_ / 2, 0, Math.PI * 2, true); + this.writingContext_.fill(); +}; + + +/** + * Draw a poly-line given the list of array of points. + * + * @param {string} color Color to draw the line. + * @param {!Array.<!i18n.input.hwt.StrokeHandler.Point>} points The array of + * points. + * @param {number=} opt_start The start point to draw. + * @private + */ +i18n.input.hwt.Canvas.prototype.drawLine_ = function(color, points, opt_start) { + var start = opt_start || 0; + if (start == (points.length - 1)) { + // If there's only one point. + this.drawPoint_(color, points[0]); + } else { + this.writingContext_.beginPath(); + this.writingContext_.strokeStyle = color; + this.writingContext_.fillStyle = 'none'; + this.writingContext_.lineWidth = this.inkWidth_; + this.writingContext_.lineCap = 'round'; + this.writingContext_.lineJoin = 'round'; + this.writingContext_.moveTo(points[start].x, points[start].y); + for (var i = start + 1; i < points.length; i++) { + this.writingContext_.lineTo(points[i].x, points[i].y); + } + this.writingContext_.stroke(); + } +}; + + +/** + * Add a point to the current stroke. + * + * @param {!i18n.input.hwt.StrokeHandler.Point} point The point. + * @private + */ +i18n.input.hwt.Canvas.prototype.addPoint_ = function(point) { + point.time -= this.startTime_; + this.stroke_.push(point); + var len = this.stroke_.length; + var start = Math.max(len - 2, 0); + this.drawLine_(this.inkColor_, this.stroke_, start); +}; + + +/** + * Callback for the start of a stroke. + * + * @param {!i18n.input.hwt.StrokeHandler.StrokeEvent} e The stroke event. + * @private + */ +i18n.input.hwt.Canvas.prototype.onStrokeStart_ = function(e) { + this.stroke_ = []; + var point = e.point; + if (this.strokeList.length == 0) { + this.startTime_ = point.time; + } + this.addPoint_(e.point); + e.preventDefault(); + + goog.dom.classlist.add(this.getElement(), i18n.input.hwt.css.IME_INIT_OPAQUE); +}; + + +/** + * Callback for the stroke event. + * + * @param {!i18n.input.hwt.StrokeHandler.StrokeEvent} e The stroke event. + * @private + */ +i18n.input.hwt.Canvas.prototype.onStroke_ = function(e) { + this.addPoint_(e.point); + e.preventDefault(); +}; + + +/** + * Callback for the end of a stroke. + * + * @param {i18n.input.hwt.StrokeHandler.StrokeEvent} e The stroke event. + * @private + */ +i18n.input.hwt.Canvas.prototype.onStrokeEnd_ = function(e) { + this.strokeList.push(this.stroke_); + e.preventDefault(); + this.dispatchEvent(new goog.events.Event( + i18n.input.hwt.EventType.RECOGNITION_READY)); +}; + + +/** + * Clears the writing canvas. + */ +i18n.input.hwt.Canvas.prototype.reset = function() { + goog.log.info(this.logger_, 'clear ' + this.writingCanvas_.width + 'x' + + this.writingCanvas_.height); + this.writingContext_.clearRect( + 0, 0, this.writingCanvas_.width, this.writingCanvas_.height); + this.strokeList = []; + this.stroke_ = []; + this.strokeHandler_.reset(); +}; + + +/** @override */ +i18n.input.hwt.Canvas.prototype.disposeInternal = function() { + goog.dispose(this.handler_); + goog.base(this, 'disposeInternal'); +}; + + +/** + * Gets the width of the canvas. + * + * @return {number} The width of the canvas. + */ +i18n.input.hwt.Canvas.prototype.getWidth = function() { + return this.writingCanvas_.width; +}; + + +/** + * Gets the height of the canvas. + * + * @return {number} The height of the canvas. + */ +i18n.input.hwt.Canvas.prototype.getHeight = function() { + return this.writingCanvas_.height; +}; + + +/** + * True if user is drawing. + * + * @return {boolean} True if is drawing. + */ +i18n.input.hwt.Canvas.prototype.isDrawing = function() { + return this.strokeHandler_.drawing; +}; + + +/** + * True if the canvas is clean without strokes. + * + * @return {boolean} True if the canvas is clean. + */ +i18n.input.hwt.Canvas.prototype.isClean = function() { + return !this.strokeHandler_.drawing && this.strokeList.length == 0; +}; + + +/** + * Sets the size of the canvas. + * + * @param {number=} opt_height The height. + * @param {number=} opt_width The width. + */ +i18n.input.hwt.Canvas.prototype.setSize = function(opt_height, opt_width) { + if (opt_height && this.writingCanvas_.height != opt_height || + opt_width && this.writingCanvas_.width != opt_width) { + this.reset(); + } + if (opt_height) { + this.writingCanvas_.height = opt_height; + } + if (opt_width) { + this.writingCanvas_.width = opt_width; + } +}; + + +/** + * Gets the stroke handler. + * + * @return {!i18n.input.hwt.StrokeHandler} The stroke handler. + */ +i18n.input.hwt.Canvas.prototype.getStrokeHandler = function() { + return this.strokeHandler_; +}; + + +/** + * Exports message to screen reader. + * + * @param {!goog.events.BrowserEvent} e . + * @private + */ +i18n.input.hwt.Canvas.prototype.onMouseOver_ = function(e) { + this.announcer_.say(i18n.input.hwt.Canvas.MSG_INPUTTOOLS_HWT_PANEL, + goog.a11y.aria.LivePriority.ASSERTIVE); +}; diff --git a/third_party/google_input_tools/src/chrome/os/inputview/conditionname.js b/third_party/google_input_tools/src/chrome/os/inputview/conditionname.js new file mode 100644 index 0000000..46eb31f --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/conditionname.js @@ -0,0 +1,30 @@ +// 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.ConditionName'); + + +/** + * The conditions used in layout files. + * + * @enum {string} + */ +i18n.input.chrome.inputview.ConditionName = { + SHOW_COMPACT_LAYOUT_SWITCHER: 'showCompactLayoutSwitcher', + SHOW_ALTGR: 'showAltGr', + SHOW_HANDWRITING_SWITCHER: 'showHandwritingSwitcher', + SHOW_MENU: 'showMenu', + SHOW_GLOBE_OR_SYMBOL: 'showGlobeOrSymbol', + SHOW_EN_SWITCHER_KEY: 'showEnSwitcherKey' +}; + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/config/compact_letter_characters.js b/third_party/google_input_tools/src/chrome/os/inputview/config/compact_letter_characters.js new file mode 100644 index 0000000..5a53cf2 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/config/compact_letter_characters.js @@ -0,0 +1,512 @@ +// 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.content.compact.letter'); + +goog.require('i18n.input.chrome.inputview.MoreKeysShiftOperation'); +goog.require('i18n.input.chrome.inputview.content.constants'); + +goog.scope(function() { +var NonLetterKeys = i18n.input.chrome.inputview.content.constants.NonLetterKeys; +var MoreKeysShiftOperation = i18n.input.chrome.inputview.MoreKeysShiftOperation; + + +/** + * Common qwerty Letter keyset characters. + * + * @return {!Array.<!Object>} + */ +i18n.input.chrome.inputview.content.compact.letter.keyQwertyCharacters = + function() { + return [ + /* 0 */ { 'text': 'q', 'hintText': '1' }, + /* 1 */ { 'text': 'w', 'hintText': '2' }, + /* 2 */ { 'text': 'e', 'hintText': '3', + 'moreKeys': ['\u00E8', '\u00E9', '\u00EA', '\u00EB', '\u0113']}, + /* 3 */ { 'text': 'r', 'hintText': '4' }, + /* 4 */ { 'text': 't', 'hintText': '5' }, + /* 5 */ { 'text': 'y', 'hintText': '6' }, + /* 6 */ { 'text': 'u', 'hintText': '7', + 'moreKeys': ['\u00FB', '\u00FC', '\u00F9', '\u00FA', '\u016B']}, + /* 7 */ { 'text': 'i', 'hintText': '8', + 'moreKeys': ['\u00EE', '\u00EF', '\u00ED', '\u012B', '\u00EC']}, + /* 8 */ { 'text': 'o', 'hintText': '9', + 'moreKeys': ['\u00F4', '\u00F6', '\u00F2', '\u00F3', '\u0153', '\u00F8', + '\u014D', '\u00F5']}, + /* 9 */ { 'text': 'p', 'hintText': '0' }, + /* 10 */ NonLetterKeys.BACKSPACE, + /* 11 */ { 'text': 'a', 'marginLeftPercent': 0.33, + 'moreKeys': ['\u00E0', '\u00E1', '\u00E2', '\u00E4', '\u00E6', '\u00E3', + '\u00E5', '\u0101']}, + /* 12 */ { 'text': 's', + 'moreKeys': ['\u00DF']}, + /* 13 */ { 'text': 'd' }, + /* 14 */ { 'text': 'f' }, + /* 15 */ { 'text': 'g' }, + /* 16 */ { 'text': 'h' }, + /* 17 */ { 'text': 'j' }, + /* 18 */ { 'text': 'k' }, + /* 19 */ { 'text': 'l' }, + /* 20 */ NonLetterKeys.ENTER, + /* 21 */ NonLetterKeys.LEFT_SHIFT, + /* 22 */ { 'text': 'z' }, + /* 23 */ { 'text': 'x' }, + /* 24 */ { 'text': 'c', + 'moreKeys': ['\u00E7']}, + /* 25 */ { 'text': 'v' }, + /* 26 */ { 'text': 'b' }, + /* 27 */ { 'text': 'n', + 'moreKeys': ['\u00F1']}, + /* 28 */ { 'text': 'm' }, + /* 29 */ { 'text': '!', + 'moreKeys': ['\u00A1']}, + /* 30 */ { 'text': '?', + 'moreKeys': ['\u00BF']}, + /* 31 */ NonLetterKeys.RIGHT_SHIFT, + /* 32 */ NonLetterKeys.SWITCHER, + /* 33 */ NonLetterKeys.GLOBE, + /* 34 */ NonLetterKeys.MENU, + /* 35 */ { 'text': '/', 'isGrey': true }, + /* 36 */ NonLetterKeys.SPACE, + /* 37 */ { 'text': ',', 'isGrey': true }, + /* 38 */ { 'text': '.', 'isGrey': true, + 'moreKeys': ['\u0023', '\u0021', '\u005C', '\u003F', '\u002D', '\u003A', + '\u0027', '\u0040']}, + /* 39 */ NonLetterKeys.HIDE + ]; +}; + + +/** + * Belgian Letter keyset characters. + * + * @return {!Array.<!Object>} + */ +i18n.input.chrome.inputview.content.compact.letter.keyBelgianCharacters = + function() { + var data = + i18n.input.chrome.inputview.content.compact.letter.keyQwertyCharacters(); + data[2]['moreKeys'] = + ['\u0117', '\u00E8', '\u0119', '\u00E9', '\u00EA', '\u00EB', '\u0113']; + data[5]['moreKeys'] = ['\u0133']; + data[7]['moreKeys'] = + ['\u00EE', '\u00EF', '\u012F', '\u0133', '\u00ED', '\u012B', '\u00EC']; + data[12]['moreKeys'] = undefined; + data[24]['moreKeys'] = undefined; + data[27]['moreKeys'] = ['\u00F1', '\u0144']; + return data; +}; + + +/** + * Icelandic Letter keyset characters. + * + * @return {!Array.<!Object>} + */ +i18n.input.chrome.inputview.content.compact.letter.keyIcelandicCharacters = + function() { + var data = + i18n.input.chrome.inputview.content.compact.letter.keyQwertyCharacters(); + data[2]['moreKeys'] = ['\u00E9', '\u00EB', '\u00E8', '\u00EA', '\u0119', + '\u0117', '\u0113']; // e + data[4]['moreKeys'] = ['\u00FE']; // t + data[5]['moreKeys'] = ['\u00FD', '\u00FF']; // y + data[7]['moreKeys'] = ['\u00ED', '\u00EF', '\u00EE', '\u00EC', '\u012F', + '\u012B']; // i + data[12]['moreKeys'] = undefined; + data[13]['moreKeys'] = ['\u00F0']; + data[24]['moreKeys'] = undefined; + return data; +}; + + +/** + * Qwertz Germany Letter keyset characters. + * + * @return {!Array.<!Object>} + */ +i18n.input.chrome.inputview.content.compact.letter.keyQwertzCharacters = + function() { + var data = + i18n.input.chrome.inputview.content.compact.letter.keyQwertyCharacters(); + data[2]['moreKeys'] = ['\u00E9', '\u00E8', '\u00EA', '\u00EB', '\u0117']; + data[7]['moreKeys'] = undefined; + data[12]['moreKeys'] = ['\u00DF', '\u015B', '\u0161']; + data[24]['moreKeys'] = undefined; + data[27]['moreKeys'] = ['\u00F1', '\u0144']; + + data[5].text = 'z'; + data[22].text = 'y'; + return data; +}; + + +/** + * Common azerty Letter keyset characters. + * + * @return {!Array.<!Object>} + */ +i18n.input.chrome.inputview.content.compact.letter.keyAzertyCharacters = + function() { + return [ + /* 0 */ { 'text': 'a', 'hintText': '1', + 'moreKeys': ['\u00E0', '\u00E2', '\u00E6', '\u00E1', '\u00E4', + '\u00E3', '\u00E5', '\u0101', '\u00AA']}, + /* 1 */ { 'text': 'z', 'hintText': '2' }, + /* 2 */ { 'text': 'e', 'hintText': '3', + 'moreKeys': ['\u00E9', '\u00E8', '\u00EA', '\u00EB', '\u0119', + '\u0117', '\u0113']}, + /* 3 */ { 'text': 'r', 'hintText': '4' }, + /* 4 */ { 'text': 't', 'hintText': '5' }, + /* 5 */ { 'text': 'y', 'hintText': '6', + 'moreKeys': ['\u00FF']}, + /* 6 */ { 'text': 'u', 'hintText': '7', + 'moreKeys': ['\u00F9', '\u00FB', '\u00FC', '\u00FA', '\u016B']}, + /* 7 */ { 'text': 'i', 'hintText': '8', + 'moreKeys': ['\u00EE', '\u00EF', '\u00EC', '\u00ED', '\u012F', + '\u012B']}, + /* 8 */ { 'text': 'o', 'hintText': '9', + 'moreKeys': ['\u00F4', '\u0153', '\u00F6', '\u00F2', '\u00F3', '\u00F5', + '\u00F8', '\u014D', '\u00BA']}, + /* 9 */ { 'text': 'p', 'hintText': '0' }, + /* 10 */ NonLetterKeys.BACKSPACE, + /* 11 */ { 'text': 'q' }, + /* 12 */ { 'text': 's' }, + /* 13 */ { 'text': 'd' }, + /* 14 */ { 'text': 'f' }, + /* 15 */ { 'text': 'g' }, + /* 16 */ { 'text': 'h' }, + /* 17 */ { 'text': 'j' }, + /* 18 */ { 'text': 'k' }, + /* 19 */ { 'text': 'l' }, + /* 20 */ { 'text': 'm' }, + /* 21 */ NonLetterKeys.ENTER, + /* 22 */ NonLetterKeys.LEFT_SHIFT, + /* 23 */ { 'text': 'w' }, + /* 24 */ { 'text': 'x' }, + /* 25 */ { 'text': 'c', + 'moreKeys': ['\u00E7', '\u0107', '\u010D']}, + /* 26 */ { 'text': 'v' }, + /* 27 */ { 'text': 'b' }, + /* 28 */ { 'text': 'n' }, + /* 29 */ { 'text': '\'', + 'moreKeys': ['\u2018', '\u201A', '\u2019', '\u2039', '\u203A']}, + /* 30 */ { 'text': '!', + 'moreKeys': ['\u00A1']}, + /* 31 */ { 'text': '?', + 'moreKeys': ['\u00BF']}, + /* 32 */ NonLetterKeys.RIGHT_SHIFT, + /* 33 */ NonLetterKeys.SWITCHER, + /* 34 */ NonLetterKeys.GLOBE, + /* 35 */ NonLetterKeys.MENU, + /* 36 */ { 'text': '/', 'isGrey': true }, + /* 37 */ NonLetterKeys.SPACE, + /* 38 */ { 'text': ',', 'isGrey': true }, + /* 39 */ { 'text': '.', 'isGrey': true, + 'moreKeys': ['\u0023', '\u0021', '\u005C', '\u003F', '\u002D', '\u003A', + '\u0027', '\u0040']}, + /* 40 */ NonLetterKeys.HIDE + ]; +}; + + +/** + * Basic nordic Letter keyset characters(based on Finish). + * + * @return {!Array.<!Object>} + */ +i18n.input.chrome.inputview.content.compact.letter.keyNordicCharacters = + function() { + return [ + /* 0 */ { 'text': 'q', 'hintText': '1' }, + /* 1 */ { 'text': 'w', 'hintText': '2' }, + /* 2 */ { 'text': 'e', 'hintText': '3' }, + /* 3 */ { 'text': 'r', 'hintText': '4' }, + /* 4 */ { 'text': 't', 'hintText': '5' }, + /* 5 */ { 'text': 'y', 'hintText': '6' }, + /* 6 */ { 'text': 'u', 'hintText': '7', + 'moreKeys': ['\u00FC']}, + /* 7 */ { 'text': 'i', 'hintText': '8' }, + /* 8 */ { 'text': 'o', 'hintText': '9', + 'moreKeys': ['\u00F8', '\u00F4', '\u00F2', '\u00F3', '\u00F5', '\u0153', + '\u014D']}, + /* 9 */ { 'text': 'p', 'hintText': '0' }, + /* 10 */ { 'text': '\u00e5' }, + /* 11 */ NonLetterKeys.BACKSPACE, + /* 12 */ { 'text': 'a', + 'moreKeys': ['\u00E6', '\u00E0', '\u00E1', '\u00E2', '\u00E3', + '\u0101']}, + /* 13 */ { 'text': 's', + 'moreKeys': ['\u0161', '\u00DF', '\u015B']}, + /* 14 */ { 'text': 'd' }, + /* 15 */ { 'text': 'f' }, + /* 16 */ { 'text': 'g' }, + /* 17 */ { 'text': 'h' }, + /* 18 */ { 'text': 'j' }, + /* 19 */ { 'text': 'k' }, + /* 20 */ { 'text': 'l' }, + /* 21 */ { 'text': '\u00f6', + 'moreKeys': ['\u00F8']}, + /* 22 */ { 'text': '\u00e4', + 'moreKeys': ['\u00E6']}, + /* 23 */ NonLetterKeys.ENTER, + /* 24 */ NonLetterKeys.LEFT_SHIFT, + /* 25 */ { 'text': 'z', 'marginLeftPercent': 0.33, + 'moreKeys': ['\u017E', '\u017A', '\u017C']}, + /* 26 */ { 'text': 'x' }, + /* 27 */ { 'text': 'c' }, + /* 28 */ { 'text': 'v' }, + /* 29 */ { 'text': 'b' }, + /* 30 */ { 'text': 'n' }, + /* 31 */ { 'text': 'm' }, + /* 32 */ { 'text': '!', + 'moreKeys': ['\u00A1']}, + /* 33 */ { 'text': '?', 'marginRightPercent': 0.33, + 'moreKeys': ['\u00BF']}, + /* 34 */ NonLetterKeys.RIGHT_SHIFT, + /* 35 */ NonLetterKeys.SWITCHER, + /* 36 */ NonLetterKeys.GLOBE, + /* 37 */ NonLetterKeys.MENU, + /* 38 */ { 'text': '/', 'isGrey': true }, + /* 39 */ NonLetterKeys.SPACE, + /* 40 */ { 'text': ',', 'isGrey': true }, + /* 41 */ { 'text': '.', 'isGrey': true, + 'moreKeys': ['\u0023', '\u0021', '\u005C', '\u003F', '\u002D', '\u003A', + '\u0027', '\u0040']}, + /* 42 */ NonLetterKeys.HIDE + ]; +}; + + +/** + * Sweden nordic Letter keyset characters. + * + * @return {!Array.<!Object>} + */ +i18n.input.chrome.inputview.content.compact.letter.keySwedenCharacters = + function() { + var data = + i18n.input.chrome.inputview.content.compact.letter.keyNordicCharacters(); + data[2]['moreKeys'] = ['\u00E9', '\u00E8', '\u00EA', '\u00EB', + '\u0119']; // e + data[3]['moreKeys'] = ['\u0159']; // r + data[4]['moreKeys'] = ['\u0165', '\u00FE']; // t + data[5]['moreKeys'] = ['\u00FD', '\u00FF']; // y + data[6]['moreKeys'] = ['\u00FC', '\u00FA', '\u00F9', '\u00FB', + '\u016B']; // u + data[7]['moreKeys'] = ['\u00ED', '\u00EC', '\u00EE', '\u00EF']; // i + data[8]['moreKeys'] = ['\u00F3', '\u00F2', '\u00F4', '\u00F5', + '\u014D']; // o + data[12]['moreKeys'] = ['\u00E1', '\u00E0', '\u00E2', '\u0105', + '\u00E3']; // a + data[13]['moreKeys'] = ['\u015B', '\u0161', '\u015F', '\u00DF']; // s + data[14]['moreKeys'] = ['\u00F0', '\u010F']; // d + data[20]['moreKeys'] = ['\u0142']; // l + data[21]['moreKeys'] = ['\u00F8', '\u0153']; + data[22]['moreKeys'] = ['\u00E6']; + data[25]['moreKeys'] = ['\u017A', '\u017E', '\u017C']; //z + data[27]['moreKeys'] = ['\u00E7', '\u0107', '\u010D']; //c + data[30]['moreKeys'] = ['\u0144', '\u00F1', '\u0148']; //n + + return data; +}; + + +/** + * Norway nordic Letter keyset characters. + * + * @return {!Array.<!Object>} + */ +i18n.input.chrome.inputview.content.compact.letter.keyNorwayCharacters = + function() { + var data = + i18n.input.chrome.inputview.content.compact.letter.keyNordicCharacters(); + data[2]['moreKeys'] = ['\u00E9', '\u00E8', '\u00EA', '\u00EB', '\u0119', + '\u0117', '\u0113']; // e + data[6]['moreKeys'] = ['\u00FC', '\u00FB', '\u00F9', '\u00FA', + '\u016B']; // u + data[8]['moreKeys'] = ['\u00F4', '\u00F2', '\u00F3', '\u00F6', '\u00F5', + '\u0153', '\u014D']; // o + data[12]['moreKeys'] = ['\u00E0', '\u00E4', '\u00E1', '\u00E2', '\u00E3', + '\u0101']; // a + data[13]['moreKeys'] = undefined; //s + data[21]['moreKeys'] = ['\u00F6']; + data[22]['moreKeys'] = ['\u00E4']; + data[25]['moreKeys'] = undefined; //z + + data[21]['text'] = '\u00f8'; + data[22]['text'] = '\u00e6'; + + return data; +}; + + +/** + * Denmark nordic Letter keyset characters. + * + * @return {!Array.<!Object>} + */ +i18n.input.chrome.inputview.content.compact.letter.keyDenmarkCharacters = + function() { + var data = + i18n.input.chrome.inputview.content.compact.letter.keyNordicCharacters(); + data[2]['moreKeys'] = ['\u00E9', '\u00EB']; // e + data[5]['moreKeys'] = ['\u00FD', '\u00FF']; // y + data[6]['moreKeys'] = ['\u00FA', '\u00FC', '\u00FB', '\u00F9', + '\u016B']; // u + data[7]['moreKeys'] = ['\u00ED', '\u00EF']; // i + data[8]['moreKeys'] = ['\u00F3', '\u00F4', '\u00F2', '\u00F5', + '\u0153', '\u014D']; // o + data[12]['moreKeys'] = ['\u00E1', '\u00E4', '\u00E0', '\u00E2', '\u00E3', + '\u0101']; // a + data[13]['moreKeys'] = ['\u00DF', '\u015B', '\u0161']; // s + data[14]['moreKeys'] = ['\u00F0']; // d + data[20]['moreKeys'] = ['\u0142']; // l + data[21]['moreKeys'] = ['\u00E4']; + data[22]['moreKeys'] = ['\u00F6']; + data[25]['moreKeys'] = undefined; //z + data[30]['moreKeys'] = ['\u00F1', '\u0144']; //n + + data[21]['text'] = '\u00e6'; + data[22]['text'] = '\u00f8'; + + return data; +}; + + +/** + * Pinyin keyset characters. + * + * @return {!Array.<!Object>} + */ +i18n.input.chrome.inputview.content.compact.letter.keyPinyinCharacters = + function() { + var data = [ + /* 0 */ { 'text': 'q', 'hintText': '1', + 'moreKeys': ['\u0051', '\u0071']}, + /* 1 */ { 'text': 'w', 'hintText': '2', + 'moreKeys': ['\u0057', '\u0077']}, + /* 2 */ { 'text': 'e', 'hintText': '3', + 'moreKeys': ['\u0045', '\u0065']}, + /* 3 */ { 'text': 'r', 'hintText': '4', + 'moreKeys': ['\u0052', '\u0072']}, + /* 4 */ { 'text': 't', 'hintText': '5', + 'moreKeys': ['\u0054', '\u0074']}, + /* 5 */ { 'text': 'y', 'hintText': '6', + 'moreKeys': ['\u0059', '\u0079']}, + /* 6 */ { 'text': 'u', 'hintText': '7', + 'moreKeys': ['\u0055', '\u0075']}, + /* 7 */ { 'text': 'i', 'hintText': '8', + 'moreKeys': ['\u0049', '\u0069'] }, + /* 8 */ { 'text': 'o', 'hintText': '9', + 'moreKeys': ['\u004F', '\u006F']}, + /* 9 */ { 'text': 'p', 'hintText': '0', + 'moreKeys': ['\u0050', '\u0070']}, + /* 10 */ NonLetterKeys.BACKSPACE, + /* 11 */ { 'text': 'a', 'hintText': '@', 'marginLeftPercent': 0.33, + 'moreKeys': ['\u0041', '\u0061']}, + /* 12 */ { 'text': 's', 'hintText': '\u00D7', + 'moreKeys': ['\u0053', '\u0073']}, + /* 13 */ { 'text': 'd', 'hintText': '+', + 'moreKeys': ['\u0044', '\u0064']}, + /* 14 */ { 'text': 'f', 'hintText': '\uff0d', + 'moreKeys': ['\u0046', '\u0066']}, + /* 15 */ { 'text': 'g', 'hintText': '=', + 'moreKeys': ['\u0047', '\u0067']}, + /* 16 */ { 'text': 'h', 'hintText': '\uff0f', + 'moreKeys': ['\u0048', '\u0068']}, + /* 17 */ { 'text': 'j', 'hintText': '#', + 'moreKeys': ['\u004a', '\u006a']}, + /* 18 */ { 'text': 'k', 'hintText': '\uff08', + 'moreKeys': ['\u004b', '\u006b']}, + /* 19 */ { 'text': 'l', 'hintText': '\uff09', + 'moreKeys': ['\u004c', '\u006c']}, + /* 20 */ NonLetterKeys.ENTER, + /* 21 */ NonLetterKeys.LEFT_SHIFT, + /* 22 */ { 'text': 'z', 'hintText': '\u3001', + 'moreKeys': ['\u005a', '\u007a']}, + /* 23 */ { 'text': 'x', 'hintText': '\uff1a', + 'moreKeys': ['\u0058', '\u0078']}, + /* 24 */ { 'text': 'c', 'hintText': '\"', + 'moreKeys': ['\u0043', '\u0063']}, + /* 25 */ { 'text': 'v', 'hintText': '\uff1f', + 'moreKeys': ['\u0056', '\u0076']}, + /* 26 */ { 'text': 'b', 'hintText': '\uff01', + 'moreKeys': ['\u0042', '\u0062']}, + /* 27 */ { 'text': 'n', 'hintText': '\uff5e', + 'moreKeys': ['\u004e', '\u006e']}, + /* 28 */ { 'text': 'm', 'hintText': '.', + 'moreKeys': ['\u004d', '\u006d']}, + /* 29 */ { 'text': '\uff01', + 'moreKeys': ['\u00A1']}, + /* 30 */ { 'text': '\uff1f', + 'moreKeys': ['\u00BF']}, + /* 31 */ NonLetterKeys.RIGHT_SHIFT, + /* 32 */ NonLetterKeys.SWITCHER, + /* 33 */ NonLetterKeys.GLOBE, + /* 34 */ NonLetterKeys.MENU, + /* 35 */ { 'text': '\uff0c', 'isGrey': true }, + /* 36 */ NonLetterKeys.SPACE, + /* 37 */ { 'text': '\u3002', 'isGrey': true }, + /* 38 */ NonLetterKeys.SWITCHER, + /* 39 */ NonLetterKeys.HIDE + ]; + for (var i = 0; i <= 9; i++) { + data[i]['moreKeysShiftOperation'] = MoreKeysShiftOperation.TO_LOWER_CASE; + } + for (var i = 11; i <= 19; i++) { + data[i]['moreKeysShiftOperation'] = MoreKeysShiftOperation.TO_LOWER_CASE; + } + for (var i = 22; i <= 28; i++) { + data[i]['moreKeysShiftOperation'] = MoreKeysShiftOperation.TO_LOWER_CASE; + } + return data; +}; + + +/** + * English mode of pinyin keyset characters. + * + * @return {!Array.<!Object>} + */ +i18n.input.chrome.inputview.content.compact.letter.keyEnCharacters = + function() { + var data = + i18n.input.chrome.inputview.content.compact.letter.keyPinyinCharacters(); + for (var i = 0; i <= 9; i++) { + data[i]['moreKeys'].pop(); + } + for (var i = 11; i <= 19; i++) { + data[i]['moreKeys'].pop(); + } + for (var i = 22; i <= 28; i++) { + data[i]['moreKeys'].pop(); + } + data[12]['hintText'] = '*'; + data[14]['hintText'] = '\u002d'; + data[16]['hintText'] = '/'; + data[18]['hintText'] = '\u0028'; + data[19]['hintText'] = '\u0029'; + data[22]['hintText'] = '\u0027'; + data[23]['hintText'] = '\u003a'; + data[25]['hintText'] = '\u003f'; + data[26]['hintText'] = '\u0021'; + data[27]['hintText'] = '\u007e'; + data[28]['hintText'] = '\u2026'; + data[29]['text'] = '\u0021'; + data[30]['text'] = '\u003f'; + data[35]['text'] = '\u002c'; + data[37]['text'] = '.'; + return data; +}; +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/config/compact_more_characters.js b/third_party/google_input_tools/src/chrome/os/inputview/config/compact_more_characters.js new file mode 100644 index 0000000..81cdb95 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/config/compact_more_characters.js @@ -0,0 +1,160 @@ +// 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.content.compact.more'); + +goog.require('i18n.input.chrome.inputview.content.constants'); + +goog.scope(function() { +var NonLetterKeys = i18n.input.chrome.inputview.content.constants.NonLetterKeys; + + +/** + * North American More keyset characters. + * + * @return {!Array.<!Object>} + */ +i18n.input.chrome.inputview.content.compact.more.keyNAMoreCharacters = + function() { + return [ + /* 0 */ { 'text': '~' }, + /* 1 */ { 'text': '`' }, + /* 2 */ { 'text': '|' }, + // Keep in sync with rowkeys_symbols_shift1.xml in android input tool. + /* 3 */ { 'text': '\u2022', + 'moreKeys': ['\u266A', '\u2665', '\u2660', '\u2666', '\u2663']}, + /* 4 */ { 'text': '\u23B7' }, + // Keep in sync with rowkeys_symbols_shift1.xml in android input tool. + /* 5 */ { 'text': '\u03C0', + 'moreKeys': ['\u03A0']}, + /* 6 */ { 'text': '\u00F7' }, + /* 7 */ { 'text': '\u00D7' }, + /* 8 */ { 'text': '\u00B6', + 'moreKeys': ['\u00A7']}, + /* 9 */ { 'text': '\u0394' }, + /* 10 */ NonLetterKeys.BACKSPACE, + /* 11 */ { 'text': '\u00A3', 'marginLeftPercent': 0.33 }, + /* 12 */ { 'text': '\u00A2' }, + /* 13 */ { 'text': '\u20AC' }, + /* 14 */ { 'text': '\u00A5' }, + // Keep in sync with rowkeys_symbols_shift2.xml in android input tool. + /* 15 */ { 'text': '^', + 'moreKeys': ['\u2191', '\u2193', '\u2190', '\u2192']}, + /* 16 */ { 'text': '\u00B0', + 'moreKeys': ['\u2032', '\u2033']}, + /* 17 */ { 'text': '=', + 'moreKeys': ['\u2260', '\u2248', '\u221E']}, + /* 18 */ { 'text': '{' }, + /* 19 */ { 'text': '}' }, + /* 20 */ NonLetterKeys.ENTER, + /* 21 */ NonLetterKeys.SWITCHER, + /* 22 */ { 'text': '\\' }, + /* 23 */ { 'text': '\u00A9' }, + /* 24 */ { 'text': '\u00AE' }, + /* 25 */ { 'text': '\u2122' }, + /* 26 */ { 'text': '\u2105' }, + /* 27 */ { 'text': '[' }, + /* 28 */ { 'text': ']' }, + /* 29 */ { 'text': '\u00A1' }, + /* 30 */ { 'text': '\u00BF' }, + /* 31 */ NonLetterKeys.SWITCHER, + /* 32 */ NonLetterKeys.SWITCHER, + /* 33 */ { 'text': '<', 'isGrey': true, + 'moreKeys': ['\u2039', '\u2264', '\u00AB']}, + /* 34 */ NonLetterKeys.MENU, + /* 35 */ { 'text': '>', 'isGrey': true, + 'moreKeys': ['\u203A', '\u2265', '\u00BB']}, + /* 36 */ NonLetterKeys.SPACE, + /* 37 */ { 'text': ',', 'isGrey': true }, + /* 38 */ { 'text': '.', 'isGrey': true, + 'moreKeys': ['\u2026']}, + /* 39 */ NonLetterKeys.HIDE + ]; +}; + + +/** + * Gets United Kingdom More keyset characters. + * + * @return {!Array.<!Object>} + */ +i18n.input.chrome.inputview.content.compact.more.keyUKMoreCharacters = + function() { + // Copy North America more characters. + var data = i18n.input.chrome.inputview.content.compact.more. + keyNAMoreCharacters(); + data[11]['text'] = '\u20AC'; // pound -> euro + data[12]['text'] = '\u00A5'; // cent -> yen + data[13]['text'] = '$'; // euro -> dollar + data[13]['moreKeys'] = ['\u00A2']; + data[14]['text'] = '\u00A2'; // yen -> cent + return data; +}; + + +/** + * Gets European More keyset characters. + * + * @return {!Array.<!Object>} + */ +i18n.input.chrome.inputview.content.compact.more.keyEUMoreCharacters = + function() { + // Copy UK more characters. + var data = i18n.input.chrome.inputview.content.compact.more. + keyUKMoreCharacters(); + data[11]['text'] = '\u00A3'; // euro -> pound + return data; +}; + + +/** + * Gets Pinyin More keyset characters. + * + * @return {!Array.<!Object>} + */ +i18n.input.chrome.inputview.content.compact.more.keyPinyinMoreCharacters = + function() { + var data = i18n.input.chrome.inputview.content.compact.more. + keyNAMoreCharacters(); + data[0]['text'] = '\uff5e'; + data[1]['text'] = '\u2194'; + data[1]['moreKeys'] = ['\u2191', '\u2193', '\u2190', '\u2192']; + data[11]['text'] = '\u00a5'; + data[11]['moreKeys'] = ['\u00a2', '\u00a3', '\u20ac']; + data[12]['text'] = '\u3010'; + data[13]['text'] = '\u3011'; + data[14]['text'] = '\u300e'; + data[15]['text'] = '\u300f'; + data[15]['moreKeys'] = undefined; + data[17]['text'] = '\u2026'; + data[17]['moreKeys'] = undefined; + data[18]['text'] = '\uff5b'; + data[19]['text'] = '\uff5d'; + data[22]['text'] = '\u003d'; + data[22]['moreKeys'] = ['\u2260', '\u2248', '\u221E']; + data[27]['text'] = '\uff3b'; + data[28]['text'] = '\uff3d'; + data[29]['text'] = '\u300a'; + data[29]['moreKeys'] = ['\u2039', '\u2264', '\u00AB']; + data[30]['text'] = '\u300b'; + data[30]['moreKeys'] = ['\u203A', '\u2265', '\u00BB']; + data[33]['text'] = '\uff01'; + data[33]['moreKeys'] = undefined; + data[35]['text'] = '\uff1f'; + data[35]['moreKeys'] = undefined; + data[37]['text'] = '\uff0c'; + data[38]['text'] = '\u3002'; + return data; +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/config/compact_numberpad_characters.js b/third_party/google_input_tools/src/chrome/os/inputview/config/compact_numberpad_characters.js new file mode 100644 index 0000000..61cb7fc --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/config/compact_numberpad_characters.js @@ -0,0 +1,69 @@ +// 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.content.compact.numberpad'); + +goog.require('i18n.input.chrome.inputview.content.constants'); + +goog.scope(function() { +var NonLetterKeys = i18n.input.chrome.inputview.content.constants.NonLetterKeys; + +/** + * Generic numberpad keyset characters. + * + * @return {!Array.<!Object>} + */ +i18n.input.chrome.inputview.content.compact.numberpad.keyNumberpadCharacters = + function() { + return [ + /* 0 */ { 'text': '-', + 'isGrey': true}, + /* 1 */ { 'text': '+', + 'isGrey': true}, + /* 2 */ { 'text': '.', + 'isGrey': true}, + /* 3 */ { 'text': '1'}, + /* 4 */ { 'text': '2'}, + /* 5 */ { 'text': '3'}, + /* 6 */ NonLetterKeys.BACKSPACE, + /* 7 */ { 'text': '*', + 'isGrey': true}, + /* 8 */ { 'text': '/', + 'isGrey': true}, + /* 9 */ { 'text': ',', + 'isGrey': true}, + /* 10 */ { 'text': '4'}, + /* 11 */ { 'text': '5'}, + /* 12 */ { 'text': '6'}, + /* 13 */ NonLetterKeys.ENTER, + /* 14 */ { 'text': '(', + 'isGrey': true}, + /* 15 */ { 'text': ')', + 'isGrey': true}, + /* 16 */ { 'text': '=', + 'isGrey': true}, + /* 17 */ { 'text': '7'}, + /* 18 */ { 'text': '8'}, + /* 19 */ { 'text': '9', + 'marginRightPercent': 0.545454}, + /* 20 */ NonLetterKeys.SPACE, + /* 21 */ { 'text': '*'}, + /* 22 */ { 'text': '0'}, + /* 23 */ { 'text': '#'}, + /* 24 */ NonLetterKeys.HIDE + ]; +}; + +// TODO(rsadam): Add phone numberpad keyset here. + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/config/compact_symbol_characters.js b/third_party/google_input_tools/src/chrome/os/inputview/config/compact_symbol_characters.js new file mode 100644 index 0000000..d6ef0cb4 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/config/compact_symbol_characters.js @@ -0,0 +1,169 @@ +// 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.content.compact.symbol'); + +goog.require('i18n.input.chrome.inputview.content.constants'); + +goog.scope(function() { +var NonLetterKeys = i18n.input.chrome.inputview.content.constants.NonLetterKeys; + + +/** + * Gets North American Symbol keyset characters. + * + * @return {!Array.<!Object>} + */ +i18n.input.chrome.inputview.content.compact.symbol.keyNASymbolCharacters = + function() { + return [ + /* 0 */ { 'text': '1', + 'moreKeys': ['\u00B9', '\u00BD', '\u2153', '\u00BC', '\u215B']}, + /* 1 */ { 'text': '2', + 'moreKeys': ['\u00B2', '\u2154']}, + /* 2 */ { 'text': '3', + 'moreKeys': ['\u00B3', '\u00BE', '\u215C']}, + /* 3 */ { 'text': '4', + 'moreKeys': ['\u2074']}, + /* 4 */ { 'text': '5', + 'moreKeys': ['\u215D']}, + /* 5 */ { 'text': '6' }, + /* 6 */ { 'text': '7', + 'moreKeys': ['\u215E']}, + /* 7 */ { 'text': '8' }, + /* 8 */ { 'text': '9' }, + /* 9 */ { 'text': '0', + 'moreKeys': ['\u207F', '\u2205']}, + /* 10 */ NonLetterKeys.BACKSPACE, + /* 11 */ { 'text': '@', 'marginLeftPercent': 0.33 }, + /* 12 */ { 'text': '#' }, + /* 13 */ { 'text': '$', + 'moreKeys': ['\u00A2', '\u00A3', '\u20AC', '\u00A5', '\u20B1']}, + /* 14 */ { 'text': '%', + 'moreKeys': ['\u2030']}, + /* 15 */ { 'text': '&' }, + // Keep in sync with rowkeys_symbols2.xml in android input tool. + /* 16 */ { 'text': '-', + 'moreKeys': ['_', '\u2013', '\u2014', '\u00B7']}, + /* 17 */ { 'text': '+', + 'moreKeys': ['\u00B1']}, + /* 18 */ { 'text': '(', + 'moreKeys': ['\u007B', '\u003C', '\u005B']}, + /* 19 */ { 'text': ')', + 'moreKeys': ['\u007D', '\u003E', '\u005D']}, + /* 20 */ NonLetterKeys.ENTER, + /* 21 */ NonLetterKeys.SWITCHER, + /* 22 */ { 'text': '\\' }, + /* 23 */ { 'text': '=' }, + /* 24 */ { 'text': '*', + 'moreKeys': ['\u2020', '\u2021', '\u2605']}, + // keep in sync with double_lqm_rqm and double_laqm_raqm in android input + // tool. + /* 25 */ { 'text': '"', + 'moreKeys': ['\u201C', '\u201E', '\u201D', '\u00AB', '\u00BB']}, + // keep in sync with single_lqm_rqm and single_laqm_raqm in android input + // tool + /* 26 */ { 'text': '\'', + 'moreKeys': ['\u2018', '\u201A', '\u2019', '\u2039', '\u203A']}, + /* 27 */ { 'text': ':' }, + /* 28 */ { 'text': ';' }, + /* 29 */ { 'text': '!', + 'moreKeys': ['\u00A1']}, + /* 30 */ { 'text': '?', + 'moreKeys': ['\u00BF']}, + /* 31 */ NonLetterKeys.SWITCHER, + /* 32 */ NonLetterKeys.SWITCHER, + /* 33 */ { 'text': '_', 'isGrey': true }, + /* 34 */ NonLetterKeys.MENU, + /* 35 */ { 'text': '/', 'isGrey': true }, + /* 36 */ NonLetterKeys.SPACE, + /* 37 */ { 'text': ',', 'isGrey': true }, + /* 38 */ { 'text': '.', 'isGrey': true, + 'moreKeys': ['\u2026']}, + /* 39 */ NonLetterKeys.HIDE + ]; +}; + + +/** + * Gets United Kingdom Symbol keyset characters. + * + * @return {!Array.<!Object>} + */ +i18n.input.chrome.inputview.content.compact.symbol.keyUKSymbolCharacters = + function() { + // Copy North America symbol characters. + var data = i18n.input.chrome.inputview.content.compact.symbol. + keyNASymbolCharacters(); + // UK uses pound sign instead of dollar sign. + data[13] = { 'text': '\u00A3', + 'moreKeys': ['\u00A2', '$', '\u20AC', '\u00A5', '\u20B1']}; + return data; +}; + + +/** + * Gets European Symbol keyset characters. + * + * @return {!Array.<!Object>} + */ +i18n.input.chrome.inputview.content.compact.symbol.keyEUSymbolCharacters = + function() { + // Copy UK symbol characters. + var data = i18n.input.chrome.inputview.content.compact.symbol. + keyUKSymbolCharacters(); + // European uses euro sign instead of pound sign. + data[13] = { 'text': '\u20AC', + 'moreKeys': ['\u00A2', '$', '\u00A3', '\u00A5', '\u20B1']}; + return data; +}; + + +/** + * Gets Pinyin Symbol keyset characters. + * + * @return {!Array.<!Object>} + */ +i18n.input.chrome.inputview.content.compact.symbol.keyPinyinSymbolCharacters = + function() { + var data = i18n.input.chrome.inputview.content.compact.symbol. + keyNASymbolCharacters(); + data[13]['text'] = '\u00A5'; + data[13]['moreKeys'] = ['\u0024', '\u00A2', '\u00A3', '\u20AC', '\u20B1']; + data[15]['text'] = '\uff0f'; + data[16]['text'] = '\uff0d'; + data[18]['text'] = '\uff08'; + data[18]['moreKeys'] = ['\uff5b', '\u300a', '\uff3b', '\u3010']; + data[19]['text'] = '\uff09'; + data[19]['moreKeys'] = ['\uff5d', '\u300b', '\uff3d', '\u3001']; + data[22]['text'] = '\u3001'; + data[24]['text'] = '\u00D7'; + data[25]['text'] = '\u201C'; + data[25]['moreKeys'] = ['\u0022', '\u00AB']; + data[26]['text'] = '\u201D'; + data[26]['moreKeys'] = ['\u0022', '\u00BB']; + data[27]['text'] = '\uff1a'; + data[28]['text'] = '\uff1b'; + data[29]['text'] = '\u2018'; + data[29]['moreKeys'] = ['\u0027', '\u2039']; + data[30]['text'] = '\u2019'; + data[30]['moreKeys'] = ['\u0027', '\u203a']; + data[33]['text'] = '\uff01'; + data[33]['moreKeys'] = ['\u00a1']; + data[35]['text'] = '\uff1f'; + data[35]['moreKeys'] = ['\u00bf']; + data[37]['text'] = '\uff0c'; + data[38]['text'] = '\u3002'; + return data; +}; +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/config/compact_util.js b/third_party/google_input_tools/src/chrome/os/inputview/config/compact_util.js new file mode 100644 index 0000000..3257a04 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/config/compact_util.js @@ -0,0 +1,378 @@ +// 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.content.compact.util'); +goog.provide('i18n.input.chrome.inputview.content.compact.util.CompactKeysetSpec'); + +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.content.constants'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); + +goog.scope(function() { +var util = i18n.input.chrome.inputview.content.compact.util; +var Css = i18n.input.chrome.inputview.Css; + + +/** + * The compact layout keyset type. + * + * @enum {string} + */ +util.CompactKeysetSpec = { + ID: 'id', + LAYOUT: 'layout', + DATA: 'data' +}; + + +/** + * @desc The name of the layout providing numbers from 0-9 and common + * symbols. + */ +util.MSG_NUMBER_AND_SYMBOL = + goog.getMsg('number and symbol layout'); + + +/** + * @desc The name of the layout providing more symbols. + */ +util.MSG_MORE_SYMBOLS = + goog.getMsg('more symbols layout'); + + +/** + * @desc The name of the main layout. + */ +util.MSG_MAIN_LAYOUT = goog.getMsg('main layout'); + + +/** + * @desc The name of the english main layout. + */ +util.MSG_ENG_MAIN_LAYOUT = goog.getMsg('english main layout'); + + +/** + * @desc The name of the english layout providing numbers from 0-9 and common. + */ +util.MSG_ENG_MORE_SYMBOLS = + goog.getMsg('english more symbols layout'); + + +/** + * @desc The name of the english layout providing more symbols. + */ +util.MSG_ENG_NUMBER_AND_SYMBOL = + goog.getMsg('english number and symbol layout'); + + +/** + * Creates the compact key data. + * + * @param {!Object} keysetSpec The keyset spec. + * @param {string} viewIdPrefix The prefix of the view. + * @param {string} keyIdPrefix The prefix of the key. + * @return {!Object} The key data. + */ +util.createCompactData = function(keysetSpec, viewIdPrefix, keyIdPrefix) { + var keyList = []; + var mapping = {}; + var keysetSpecNode = util.CompactKeysetSpec; + for (var i = 0; i < keysetSpec[keysetSpecNode.DATA].length; i++) { + var keySpec = keysetSpec[keysetSpecNode.DATA][i]; + if (keySpec == + i18n.input.chrome.inputview.content.constants.NonLetterKeys.MENU) { + keySpec['toKeyset'] = keysetSpec[keysetSpecNode.ID].split('.')[0]; + } + var id = keySpec['id'] ? keySpec['id'] : keyIdPrefix + i; + var key = util.createCompactKey(id, keySpec); + keyList.push(key); + mapping[key['spec']['id']] = viewIdPrefix + i; + } + + return { + 'keyList': keyList, + 'mapping': mapping, + 'layout': keysetSpec[keysetSpecNode.LAYOUT] + }; +}; + + +/** + * Creates a key in the compact keyboard. + * + * @param {string} id The id. + * @param {!Object} keySpec The specification. + * @return {!Object} The compact key. + */ +util.createCompactKey = function(id, keySpec) { + var spec = keySpec; + spec['id'] = id; + if (!spec['type']) + spec['type'] = i18n.input.chrome.inputview.elements.ElementType.COMPACT_KEY; + + var newSpec = {}; + for (var key in spec) { + newSpec[key] = spec[key]; + } + + return { + 'spec': newSpec + }; +}; + + +/** + * Customize the switcher keys in key characters. + * + * @param {!Array.<!Object>} keyCharacters The key characters. + * @param {!Array.<!Object>} switcherKeys The customized switcher keys. + */ +util.customizeSwitchers = function(keyCharacters, switcherKeys) { + var j = 0; + for (var i = 0; i < keyCharacters.length; i++) { + if (keyCharacters[i] == + i18n.input.chrome.inputview.content.constants.NonLetterKeys.SWITCHER) { + if (j >= switcherKeys.length) { + console.error('The number of switcher key spec is less than' + + ' the number of switcher keys in the keyset.'); + return; + } + var newSpec = switcherKeys[j++]; + for (var key in keyCharacters[i]) { + newSpec[key] = keyCharacters[i][key]; + } + keyCharacters[i] = newSpec; + } + } + if (j < switcherKeys.length - 1) { + console.error('The number of switcher key spec is more than' + + ' the number of switcher keys in the keyset.'); + } +}; + + +/** + * Generates letter, symbol and more compact keysets and load them. + * + * @param {!Object} letterKeysetSpec The spec of letter keyset. + * @param {!Object} symbolKeysetSpec The spec of symbol keyset. + * @param {!Object} moreKeysetSpec The spec of more keyset. + * @param {!function(!Object): void} onLoaded The function to call once a keyset + * data is ready. + */ +util.generateCompactKeyboard = + function(letterKeysetSpec, symbolKeysetSpec, moreKeysetSpec, onLoaded) { + // Creates compacty qwerty keyset. + var keysetSpecNode = util.CompactKeysetSpec; + util.customizeSwitchers( + letterKeysetSpec[keysetSpecNode.DATA], + [{ 'name': '?123', + 'toKeyset': symbolKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_NUMBER_AND_SYMBOL + }]); + + var data = util.createCompactData( + letterKeysetSpec, 'compactkbd-k-', 'compactkbd-k-key-'); + data['id'] = letterKeysetSpec[keysetSpecNode.ID]; + data['showMenuKey'] = true; + onLoaded(data); + + // Creates compacty symbol keyset. + util.customizeSwitchers( + symbolKeysetSpec[keysetSpecNode.DATA], + [{ 'name': '~[<', + 'toKeyset': moreKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_MORE_SYMBOLS + }, + { 'name': '~[<', + 'toKeyset': moreKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_MORE_SYMBOLS + }, + { 'name': 'abc', + 'toKeyset': letterKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_MAIN_LAYOUT + }]); + data = util.createCompactData( + symbolKeysetSpec, 'compactkbd-k-', 'compactkbd-k-key-'); + data['id'] = symbolKeysetSpec[keysetSpecNode.ID]; + data['showMenuKey'] = false; + data['noShift'] = true; + onLoaded(data); + + // Creates compact more keyset. + util.customizeSwitchers( + moreKeysetSpec[keysetSpecNode.DATA], + [{ 'name': '?123', + 'toKeyset': symbolKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_NUMBER_AND_SYMBOL + }, + { 'name': '?123', + 'toKeyset': symbolKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_NUMBER_AND_SYMBOL + }, + { 'name': 'abc', + 'toKeyset': letterKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_MAIN_LAYOUT + }]); + data = util.createCompactData(moreKeysetSpec, 'compactkbd-k-', + 'compactkbd-k-key-'); + data['id'] = moreKeysetSpec[keysetSpecNode.ID]; + data['showMenuKey'] = false; + data['noShift'] = true; + onLoaded(data); +}; + + +/** + * Generates letter, symbol and more compact keysets for + * pinyin's chinese and english mode and load them. + * + * @param {!Object} letterKeysetSpec The spec of letter keyset. + * @param {!Object} engKeysetSpec The english spec of letter keyset + * @param {!Object} symbolKeysetSpec The spec of symbol keyset. + * @param {!Object} engSymbolKeysetSpec The spec of engish symbol keyset. + * @param {!Object} moreKeysetSpec The spec of more keyset. + * @param {!Object} engMoreKeysetSpec The spec of english more keyset. + * @param {!function(!Object): void} onLoaded The function to call once a keyset + * data is ready. + */ +util.generatePinyinCompactKeyboard = function(letterKeysetSpec, engKeysetSpec, + symbolKeysetSpec, engSymbolKeysetSpec, + moreKeysetSpec, engMoreKeysetSpec, onLoaded) { + // Creates compacty qwerty keyset for pinyin. + var keysetSpecNode = util.CompactKeysetSpec; + util.customizeSwitchers( + letterKeysetSpec[keysetSpecNode.DATA], + [{ 'name': '?123', + 'toKeyset': symbolKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_NUMBER_AND_SYMBOL + }, + { 'toKeyset': engKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_ENG_MAIN_LAYOUT, + 'iconCssClass': Css.SWITCHER_CHINESE + }]); + + var data = util.createCompactData( + letterKeysetSpec, 'compactkbd-k-', 'compactkbd-k-key-'); + data['id'] = letterKeysetSpec[keysetSpecNode.ID]; + data['showMenuKey'] = true; + onLoaded(data); + + // Creates the compacty qwerty keyset for pinyin's English mode. + util.customizeSwitchers( + engKeysetSpec[keysetSpecNode.DATA], + [{ 'name': '?123', + 'toKeyset': engSymbolKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_ENG_NUMBER_AND_SYMBOL + }, + { 'toKeyset': letterKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_MAIN_LAYOUT, + 'iconCssClass': Css.SWITCHER_ENGLISH + }]); + + data = util.createCompactData( + engKeysetSpec, 'compactkbd-k-', 'compactkbd-k-key-'); + data['id'] = engKeysetSpec[keysetSpecNode.ID]; + data['showMenuKey'] = true; + onLoaded(data); + + // Creates compacty symbol keyset for pinyin. + util.customizeSwitchers( + symbolKeysetSpec[keysetSpecNode.DATA], + [{ 'name': '~[<', + 'toKeyset': moreKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_MORE_SYMBOLS + }, + { 'name': '~[<', + 'toKeyset': moreKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_MORE_SYMBOLS + }, + { 'name': 'abc', + 'toKeyset': letterKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_MAIN_LAYOUT + }]); + data = util.createCompactData( + symbolKeysetSpec, 'compactkbd-k-', 'compactkbd-k-key-'); + data['id'] = symbolKeysetSpec[keysetSpecNode.ID]; + data['showMenuKey'] = false; + data['noShift'] = true; + onLoaded(data); + + // Creates compacty symbol keyset for English mode. + util.customizeSwitchers( + engSymbolKeysetSpec[keysetSpecNode.DATA], + [{ 'name': '~[<', + 'toKeyset': engMoreKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_ENG_MORE_SYMBOLS + }, + { 'name': '~[<', + 'toKeyset': engMoreKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_ENG_MORE_SYMBOLS + }, + { 'name': 'abc', + 'toKeyset': engKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_ENG_MAIN_LAYOUT + }]); + data = util.createCompactData( + engSymbolKeysetSpec, 'compactkbd-k-', 'compactkbd-k-key-'); + data['id'] = engSymbolKeysetSpec[keysetSpecNode.ID]; + data['showMenuKey'] = false; + data['noShift'] = true; + onLoaded(data); + + // Creates compact more keyset for pinyin. + util.customizeSwitchers( + moreKeysetSpec[keysetSpecNode.DATA], + [{ 'name': '?123', + 'toKeyset': symbolKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_NUMBER_AND_SYMBOL + }, + { 'name': '?123', + 'toKeyset': symbolKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_NUMBER_AND_SYMBOL + }, + { 'name': 'abc', + 'toKeyset': letterKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_MAIN_LAYOUT + }]); + data = util.createCompactData(moreKeysetSpec, 'compactkbd-k-', + 'compactkbd-k-key-'); + data['id'] = moreKeysetSpec[keysetSpecNode.ID]; + data['showMenuKey'] = false; + data['noShift'] = true; + onLoaded(data); + + // Creates the compact more keyset of english mode. + util.customizeSwitchers( + engMoreKeysetSpec[keysetSpecNode.DATA], + [{ 'name': '?123', + 'toKeyset': engSymbolKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_ENG_NUMBER_AND_SYMBOL + }, + { 'name': '?123', + 'toKeyset': engSymbolKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_ENG_NUMBER_AND_SYMBOL + }, + { 'name': 'abc', + 'toKeyset': engKeysetSpec[keysetSpecNode.ID], + 'toKeysetName': util.MSG_ENG_MAIN_LAYOUT + }]); + data = util.createCompactData(engMoreKeysetSpec, 'compactkbd-k-', + 'compactkbd-k-key-'); + data['id'] = engMoreKeysetSpec[keysetSpecNode.ID]; + data['showMenuKey'] = false; + data['noShift'] = true; + onLoaded(data); +}; +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/config/constants.js b/third_party/google_input_tools/src/chrome/os/inputview/config/constants.js new file mode 100644 index 0000000..bf4e688 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/config/constants.js @@ -0,0 +1,81 @@ +// 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.content.constants'); + +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.StateType'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); + +goog.scope(function() { + +var ElementType = i18n.input.chrome.inputview.elements.ElementType; + +/** + * The non letter keys. + * + * @const + * @enum {Object} + */ +i18n.input.chrome.inputview.content.constants.NonLetterKeys = { + BACKSPACE: { + 'iconCssClass': i18n.input.chrome.inputview.Css.BACKSPACE_ICON, + 'type': ElementType.BACKSPACE_KEY, + 'id': 'Backspace' + }, + ENTER: { + 'iconCssClass': i18n.input.chrome.inputview.Css.ENTER_ICON, + 'type': ElementType.ENTER_KEY, + 'id': 'Enter' + }, + HIDE: { + 'iconCssClass': i18n.input.chrome.inputview.Css.HIDE_KEYBOARD_ICON, + 'type': ElementType.HIDE_KEYBOARD_KEY, + 'id': 'HideKeyboard' + }, + LEFT_SHIFT: { + 'toState': i18n.input.chrome.inputview.StateType.SHIFT, + 'iconCssClass': i18n.input.chrome.inputview.Css.SHIFT_ICON, + 'type': ElementType.MODIFIER_KEY, + 'id': 'ShiftLeft', + 'supportSticky': true + }, + RIGHT_SHIFT: { + 'toState': i18n.input.chrome.inputview.StateType.SHIFT, + 'iconCssClass': i18n.input.chrome.inputview.Css.SHIFT_ICON, + 'type': ElementType.MODIFIER_KEY, + 'id': 'ShiftRight', + 'supportSticky': true + }, + SPACE: { + 'name': ' ', + 'type': ElementType.SPACE_KEY, + 'id': 'Space' + }, + SWITCHER: { + 'type': ElementType.SWITCHER_KEY + }, + MENU: { + 'iconCssClass': i18n.input.chrome.inputview.Css.MENU_ICON, + 'type': ElementType.MENU_KEY, + 'id': 'Menu' + }, + GLOBE: { + 'iconCssClass': i18n.input.chrome.inputview.Css.GLOBE_ICON, + 'type': ElementType.GLOBE_KEY, + 'id': 'Globe' + } +}; + + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/config/contextlayoututil.js b/third_party/google_input_tools/src/chrome/os/inputview/config/contextlayoututil.js new file mode 100644 index 0000000..3cc5de1 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/config/contextlayoututil.js @@ -0,0 +1,52 @@ +// 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.content.ContextlayoutUtil'); + +goog.require('i18n.input.chrome.inputview.content.compact.util'); +goog.require('i18n.input.chrome.inputview.content.compact.util.CompactKeysetSpec'); +goog.require('i18n.input.chrome.message.ContextType'); + +goog.scope(function() { +var ContextType = i18n.input.chrome.message.ContextType; +var util = i18n.input.chrome.inputview.content.ContextlayoutUtil; +var compact = i18n.input.chrome.inputview.content.compact.util; +var keysetSpecNode = compact.CompactKeysetSpec; + + +/** + * Generates custom layouts to be displayed on specified input type contexts. + * + * + * @param {Object.<ContextType, !Object>} inputTypeToKeysetSpecMap Map from + * input types to context-specific keysets. + * @param {!function(!Object): void} onLoaded The function to call once a keyset + * data is ready. + */ +util.generateContextLayouts = function(inputTypeToKeysetSpecMap, onLoaded) { + // Creates context-specific keysets. + if (inputTypeToKeysetSpecMap) { + for (var inputType in inputTypeToKeysetSpecMap) { + var spec = inputTypeToKeysetSpecMap[inputType]; + var data = compact.createCompactData( + spec, 'compactkbd-k-', 'compactkbd-k-key-'); + data['id'] = spec[keysetSpecNode.ID]; + data['showMenuKey'] = false; + data['noShift'] = true; + data['onContext'] = inputType; + onLoaded(data); + } + } +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/config/util.js b/third_party/google_input_tools/src/chrome/os/inputview/config/util.js new file mode 100644 index 0000000..098e4aa --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/config/util.js @@ -0,0 +1,605 @@ +// 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.content.util'); + +goog.require('goog.array'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.Direction'); +goog.require('i18n.input.chrome.inputview.SpecNodeName'); +goog.require('i18n.input.chrome.inputview.StateType'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); + +goog.scope(function() { +var ElementType = i18n.input.chrome.inputview.elements.ElementType; +var SpecNodeName = i18n.input.chrome.inputview.SpecNodeName; + + +/** + * The prefix of the key id. + * + * @type {string} + * @private + */ +i18n.input.chrome.inputview.content.util.keyIdPrefix_ = 'sk-'; + + +/** + * Creates the hide keyboard key. + * + * @return {!Object} The hide keyboard key. + */ +i18n.input.chrome.inputview.content.util.createHideKeyboardKey = function() { + var spec = {}; + spec[SpecNodeName.ICON_CSS_CLASS] = + i18n.input.chrome.inputview.Css.HIDE_KEYBOARD_ICON; + spec[SpecNodeName.TYPE] = ElementType.HIDE_KEYBOARD_KEY; + spec[SpecNodeName.ID] = 'HideKeyboard'; + return i18n.input.chrome.inputview.content.util.createKey(spec); +}; + + +/** + * Creates a shift key. + * + * @param {boolean} isLeft True if this is the left shift key. + * @param {boolean=} opt_supportSticky True if support sticky shift key. + * @return {!Object} The shift key. + */ +i18n.input.chrome.inputview.content.util.createShiftKey = function(isLeft, + opt_supportSticky) { + var spec = {}; + spec[SpecNodeName.TO_STATE] = i18n.input.chrome.inputview.StateType.SHIFT; + spec[SpecNodeName.ICON_CSS_CLASS] = + i18n.input.chrome.inputview.Css.SHIFT_ICON; + spec[SpecNodeName.TYPE] = ElementType.MODIFIER_KEY; + spec[SpecNodeName.ID] = isLeft ? 'ShiftLeft' : 'ShiftRight'; + spec[SpecNodeName.SUPPORT_STICKY] = !!opt_supportSticky; + return i18n.input.chrome.inputview.content.util.createKey(spec); +}; + + +/** + * Creates a globe key. + * + * @return {!Object} The globe key. + */ +i18n.input.chrome.inputview.content.util.createGlobeKey = function() { + var spec = {}; + spec[SpecNodeName.ICON_CSS_CLASS] = + i18n.input.chrome.inputview.Css.GLOBE_ICON; + spec[SpecNodeName.TYPE] = ElementType.GLOBE_KEY; + spec[SpecNodeName.ID] = 'Globe'; + return i18n.input.chrome.inputview.content.util.createKey(spec); +}; + + +/** + * Creates a menu key. + * + * @param {string=} opt_toKeyset The compact keyboard id. + * @return {!Object} The menu key. + */ +i18n.input.chrome.inputview.content.util.createMenuKey = function( + opt_toKeyset) { + var spec = {}; + spec[SpecNodeName.ICON_CSS_CLASS] = + i18n.input.chrome.inputview.Css.MENU_ICON; + spec[SpecNodeName.TO_KEYSET] = opt_toKeyset; + spec[SpecNodeName.TYPE] = ElementType.MENU_KEY; + spec[SpecNodeName.ID] = 'Menu'; + return i18n.input.chrome.inputview.content.util.createKey(spec); +}; + + +/** + * Create the Emoji switch key. + * + * @param {string} id The emoji key id. + * @param {number} toKeyset The keyset that the tabbar represents. + * @param {i18n.input.chrome.inputview.Css} + * iconCssClass The icon css for the tabbar. + * @return {!Object} The emoji key. + */ +i18n.input.chrome.inputview.content.util.createTabBarKey = + function(id, toKeyset, iconCssClass) { + var spec = {}; + spec[SpecNodeName.ICON_CSS_CLASS] = iconCssClass; + spec[SpecNodeName.TYPE] = ElementType.TAB_BAR_KEY; + spec[SpecNodeName.ID] = id; + spec[SpecNodeName.TO_KEYSET] = toKeyset; + return i18n.input.chrome.inputview.content.util.createKey(spec); +}; + + +/** + * Create the indicator + * + * @param {string} id The indicator id. + * @return {!Object} The indicator. + */ +i18n.input.chrome.inputview.content.util.createPageIndicator = + function(id) { + var spec = {}; + spec[SpecNodeName.TYPE] = ElementType.PAGE_INDICATOR; + spec[SpecNodeName.ID] = id; + return i18n.input.chrome.inputview.content.util.createKey(spec); +}; + + +/** + * Create the back key for emoji + * + * @return {!Object} The back key. + */ +i18n.input.chrome.inputview.content.util.createBackKey = function() { + var spec = {}; + spec[SpecNodeName.ICON_CSS_CLASS] = + i18n.input.chrome.inputview.Css.EMOJI_BACK; + spec[SpecNodeName.TYPE] = ElementType.BACK_BUTTON; + spec[SpecNodeName.ID] = 'backkey'; + return i18n.input.chrome.inputview.content.util.createKey(spec); +}; + + +/** + * Creates a ctrl key. + * + * @return {!Object} The ctrl key. + */ +i18n.input.chrome.inputview.content.util.createCtrlKey = function() { + var spec = {}; + spec[SpecNodeName.TO_STATE] = i18n.input.chrome.inputview.StateType.CTRL; + spec[SpecNodeName.NAME] = 'ctrl'; + spec[SpecNodeName.TYPE] = ElementType.MODIFIER_KEY; + spec[SpecNodeName.ID] = 'ControlLeft'; + return i18n.input.chrome.inputview.content.util.createKey(spec); +}; + + +/** + * Creates a alt key. + * + * @return {!Object} The alt key. + */ +i18n.input.chrome.inputview.content.util.createAltKey = function() { + var spec = {}; + spec[SpecNodeName.TO_STATE] = i18n.input.chrome.inputview.StateType.ALT; + spec[SpecNodeName.NAME] = 'alt'; + spec[SpecNodeName.TYPE] = ElementType.MODIFIER_KEY; + spec[SpecNodeName.ID] = 'AltLeft'; + return i18n.input.chrome.inputview.content.util.createKey(spec); +}; + + +/** + * Creates a altgr key. + * + * @return {!Object} The altgr key. + */ +i18n.input.chrome.inputview.content.util.createAltgrKey = function() { + var spec = {}; + spec[SpecNodeName.TO_STATE] = i18n.input.chrome.inputview.StateType.ALTGR; + spec[SpecNodeName.NAME] = 'alt gr'; + spec[SpecNodeName.TYPE] = ElementType.MODIFIER_KEY; + spec[SpecNodeName.ID] = 'AltRight'; + return i18n.input.chrome.inputview.content.util.createKey(spec); +}; + + +/** + * Creates a key used to switch to english. + * + * @return {!Object} The enSwitcher key. + */ +i18n.input.chrome.inputview.content.util.createEnSwitcherKey = + function() { + var spec = {}; + spec[SpecNodeName.TYPE] = ElementType.EN_SWITCHER; + spec[SpecNodeName.ID] = 'enSwitcher'; + return i18n.input.chrome.inputview.content.util.createKey(spec); +}; + + +/** + * Creates a capslock key. + * + * @return {!Object} The capslock key. + */ +i18n.input.chrome.inputview.content.util.createCapslockKey = function() { + var spec = {}; + spec[SpecNodeName.TO_STATE] = i18n.input.chrome.inputview.StateType.CAPSLOCK; + spec[SpecNodeName.NAME] = 'caps lock'; + spec[SpecNodeName.TYPE] = ElementType.MODIFIER_KEY; + spec[SpecNodeName.ID] = 'OsLeft'; + return i18n.input.chrome.inputview.content.util.createKey(spec); +}; + + +/** + * Creates a enter key. + * + * @return {!Object} The enter key. + */ +i18n.input.chrome.inputview.content.util.createEnterKey = function() { + var spec = {}; + spec[SpecNodeName.ICON_CSS_CLASS] = + i18n.input.chrome.inputview.Css.ENTER_ICON; + spec[SpecNodeName.TYPE] = ElementType.ENTER_KEY; + spec[SpecNodeName.ID] = 'Enter'; + return i18n.input.chrome.inputview.content.util.createKey(spec); +}; + + +/** + * Creates a tab key. + * + * @return {!Object} The tab key. + */ +i18n.input.chrome.inputview.content.util.createTabKey = function() { + var spec = {}; + spec[SpecNodeName.ICON_CSS_CLASS] = i18n.input.chrome.inputview.Css.TAB_ICON; + spec[SpecNodeName.TYPE] = ElementType.TAB_KEY; + spec[SpecNodeName.ID] = 'Tab'; + return i18n.input.chrome.inputview.content.util.createKey(spec); +}; + + +/** + * Creates a backspace key. + * + * @return {!Object} The backspace key. + */ +i18n.input.chrome.inputview.content.util.createBackspaceKey = function() { + var spec = {}; + spec[SpecNodeName.ICON_CSS_CLASS] = + i18n.input.chrome.inputview.Css.BACKSPACE_ICON; + spec[SpecNodeName.TYPE] = ElementType.BACKSPACE_KEY; + spec[SpecNodeName.ID] = 'Backspace'; + return i18n.input.chrome.inputview.content.util.createKey(spec); +}; + + +/** + * Creates a space key. + * + * @return {!Object} The space key. + */ +i18n.input.chrome.inputview.content.util.createSpaceKey = function() { + var spec = {}; + spec[SpecNodeName.NAME] = ' '; + spec[SpecNodeName.TYPE] = ElementType.SPACE_KEY; + spec[SpecNodeName.ID] = 'Space'; + return i18n.input.chrome.inputview.content.util.createKey(spec); +}; + + +/** + * Create an IME switch key. + * + * @param {string} id . + * @param {string} name . + * @param {string} css . + * @return {!Object} The JP IME switch key. + */ +i18n.input.chrome.inputview.content.util.createIMESwitchKey = + function(id, name, css) { + var spec = {}; + spec[SpecNodeName.NAME] = name; + spec[SpecNodeName.TYPE] = ElementType.IME_SWITCH; + spec[SpecNodeName.ID] = id; + spec[SpecNodeName.TEXT_CSS_CLASS] = css; + return i18n.input.chrome.inputview.content.util.createKey(spec); +}; + + +/** + * Creates a normal key. + * + * @param {!Object} spec The specification. + * @return {!Object} The normal key. + */ +i18n.input.chrome.inputview.content.util.createNormalKey = function(spec) { + spec[SpecNodeName.TYPE] = ElementType.CHARACTER_KEY; + return i18n.input.chrome.inputview.content.util.createKey(spec); +}; + + +/** + * Creates an arrow key. + * + * @param {!i18n.input.chrome.inputview.Direction} direction The direction. + * @return {!Object} The arrow key. + */ +i18n.input.chrome.inputview.content.util.createArrowKey = function(direction) { + var spec = {}; + spec[SpecNodeName.ICON_CSS_CLASS] = + i18n.input.chrome.inputview.Css.ARROW_KEY + ' '; + if (direction == i18n.input.chrome.inputview.Direction.UP) { + spec[SpecNodeName.ID] = 'ArrowUp'; + spec[SpecNodeName.ICON_CSS_CLASS] += i18n.input.chrome.inputview.Css.UP_KEY; + spec[SpecNodeName.TYPE] = ElementType.ARROW_UP; + } else if (direction == i18n.input.chrome.inputview.Direction.DOWN) { + spec[SpecNodeName.ID] = 'ArrowDown'; + spec[SpecNodeName.ICON_CSS_CLASS] += + i18n.input.chrome.inputview.Css.DOWN_KEY; + spec[SpecNodeName.TYPE] = ElementType.ARROW_DOWN; + } else if (direction == i18n.input.chrome.inputview.Direction.LEFT) { + spec[SpecNodeName.ID] = 'ArrowLeft'; + spec[SpecNodeName.ICON_CSS_CLASS] += + i18n.input.chrome.inputview.Css.LEFT_KEY; + spec[SpecNodeName.TYPE] = ElementType.ARROW_LEFT; + } else if (direction == i18n.input.chrome.inputview.Direction.RIGHT) { + spec[SpecNodeName.ID] = 'ArrowRight'; + spec[SpecNodeName.ICON_CSS_CLASS] += + i18n.input.chrome.inputview.Css.RIGHT_KEY; + spec[SpecNodeName.TYPE] = ElementType.ARROW_RIGHT; + } + return i18n.input.chrome.inputview.content.util.createKey(spec); +}; + + +/** + * Creates a soft key. + * + * @param {!Object} spec The specification. + * @return {!Object} The soft key. + */ +i18n.input.chrome.inputview.content.util.createKey = function(spec) { + var newSpec = {}; + for (var key in spec) { + newSpec[key] = spec[key]; + } + return { + 'spec': newSpec + }; +}; + + +/** + * The physical key codes. + * + * @type {!Array.<string>} + */ +i18n.input.chrome.inputview.content.util.KEY_CODES_101 = [ + 'Backquote', + 'Digit1', + 'Digit2', + 'Digit3', + 'Digit4', + 'Digit5', + 'Digit6', + 'Digit7', + 'Digit8', + 'Digit9', + 'Digit0', + 'Minus', + 'Equal', + 'KeyQ', + 'KeyW', + 'KeyE', + 'KeyR', + 'KeyT', + 'KeyY', + 'KeyU', + 'KeyI', + 'KeyO', + 'KeyP', + 'BracketLeft', + 'BracketRight', + 'Backslash', + 'KeyA', + 'KeyS', + 'KeyD', + 'KeyF', + 'KeyG', + 'KeyH', + 'KeyJ', + 'KeyK', + 'KeyL', + 'Semicolon', + 'Quote', + 'KeyZ', + 'KeyX', + 'KeyC', + 'KeyV', + 'KeyB', + 'KeyN', + 'KeyM', + 'Comma', + 'Period', + 'Slash' +]; + + +/** + * The physical key codes for 102 keyboard. + * + * @type {!Array.<string>} + */ +i18n.input.chrome.inputview.content.util.KEY_CODES_102 = [ + 'Backquote', + 'Digit1', + 'Digit2', + 'Digit3', + 'Digit4', + 'Digit5', + 'Digit6', + 'Digit7', + 'Digit8', + 'Digit9', + 'Digit0', + 'Minus', + 'Equal', + 'KeyQ', + 'KeyW', + 'KeyE', + 'KeyR', + 'KeyT', + 'KeyY', + 'KeyU', + 'KeyI', + 'KeyO', + 'KeyP', + 'BracketLeft', + 'BracketRight', + 'KeyA', + 'KeyS', + 'KeyD', + 'KeyF', + 'KeyG', + 'KeyH', + 'KeyJ', + 'KeyK', + 'KeyL', + 'Semicolon', + 'Quote', + 'Backslash', + 'IntlBackslash', + 'KeyZ', + 'KeyX', + 'KeyC', + 'KeyV', + 'KeyB', + 'KeyN', + 'KeyM', + 'Comma', + 'Period', + 'Slash' +]; + + +/** + * Creates the key data. + * + * @param {!Array.<!Array.<string>>} keyCharacters The key characters. + * @param {string} viewIdPrefix The prefix of the view. + * @param {boolean} is102 True if it is a 102 keyboard. + * @param {boolean} hasAltGrKey True if there is altgr key. + * @param {!Array.<!Array.<number>>=} opt_keyCodes The key codes. + * @param {string=} opt_compactKeyboardId The compact keyboard id. + * @return {!Object} The key data. + */ +i18n.input.chrome.inputview.content.util.createData = function(keyCharacters, + viewIdPrefix, is102, hasAltGrKey, opt_keyCodes, opt_compactKeyboardId) { + var keyList = []; + var mapping = {}; + var keyCodes = opt_keyCodes || []; + var keyIds = is102 ? i18n.input.chrome.inputview.content.util.KEY_CODES_102 : + i18n.input.chrome.inputview.content.util.KEY_CODES_101; + for (var i = 0; i < keyCharacters.length - 1; i++) { + var spec = {}; + spec[SpecNodeName.ID] = keyIds[i]; + spec[SpecNodeName.TYPE] = ElementType.CHARACTER_KEY; + spec[SpecNodeName.CHARACTERS] = keyCharacters[i]; + spec[SpecNodeName.KEY_CODE] = keyCodes[i]; + var key = i18n.input.chrome.inputview.content.util.createKey(spec); + keyList.push(key); + } + + i18n.input.chrome.inputview.content.util.insertModifierKeys_(keyList, + is102, opt_compactKeyboardId); + for (var i = 0; i < keyList.length; i++) { + var key = keyList[i]; + mapping[key['spec'][SpecNodeName.ID]] = viewIdPrefix + i; + } + var layout = is102 ? '102kbd' : '101kbd'; + var result = []; + result[SpecNodeName.KEY_LIST] = keyList; + result[SpecNodeName.MAPPING] = mapping; + result[SpecNodeName.LAYOUT] = layout; + result[SpecNodeName.HAS_ALTGR_KEY] = hasAltGrKey; + result[SpecNodeName.HAS_COMPACT_KEYBOARD] = !!opt_compactKeyboardId; + result[SpecNodeName.SHOW_MENU_KEY] = true; + return result; +}; + + +/** + * Creates a switcher key which will switch between keyboards. + * + * @param {string} id The id. + * @param {string} name The name. + * @param {string|undefined} toKeyset The id of keyset this swicher key should + * switch to. + * @param {string} toKeysetName The name of the keyset. + * @param {string=} opt_iconCssClass The css class for the icon. + * @param {boolean=} opt_record True to record and next time the keyset will + * be recalled. + * @return {!Object} The switcher key. + */ +i18n.input.chrome.inputview.content.util.createSwitcherKey = function( + id, name, toKeyset, toKeysetName, opt_iconCssClass, opt_record) { + var spec = {}; + spec[SpecNodeName.ID] = id; + spec[SpecNodeName.NAME] = name; + spec[SpecNodeName.TO_KEYSET] = toKeyset; + spec[SpecNodeName.TO_KEYSET_NAME] = toKeysetName; + spec[SpecNodeName.ICON_CSS_CLASS] = opt_iconCssClass; + spec[SpecNodeName.TYPE] = ElementType.SWITCHER_KEY; + spec[SpecNodeName.RECORD] = !!opt_record; + return i18n.input.chrome.inputview.content.util.createKey(spec); +}; + + +/** + * Inserts modifier keys into the key list. + * + * @param {!Array.<!Object>} keyList The key list. + * @param {boolean} is102 True if it is a 102 keyboard. + * @param {string=} opt_compactKeyboardId The compact keyboard id. + * @private + */ +i18n.input.chrome.inputview.content.util.insertModifierKeys_ = function( + keyList, is102, opt_compactKeyboardId) { + goog.array.insertAt(keyList, i18n.input.chrome.inputview.content.util. + createBackspaceKey(), 13); + goog.array.insertAt(keyList, i18n.input.chrome.inputview.content.util. + createTabKey(), 14); + goog.array.insertAt(keyList, i18n.input.chrome.inputview.content.util. + createCapslockKey(), is102 ? 27 : 28); + goog.array.insertAt(keyList, i18n.input.chrome.inputview.content.util. + createEnterKey(), 40); + goog.array.insertAt(keyList, i18n.input.chrome.inputview.content.util. + createShiftKey(true), 41); + keyList.push(i18n.input.chrome.inputview.content.util.createShiftKey(false)); + i18n.input.chrome.inputview.content.util.addLastRowKeys( + keyList, is102, opt_compactKeyboardId); +}; + + +/** + * Inserts modifier keys into the key list. + * + * @param {!Array.<!Object>} keyList The key list. + * @param {boolean} is102 True if it is a 102 keyboard. + * @param {string=} opt_compactKeyboardId The compact keyboard id. + */ +i18n.input.chrome.inputview.content.util.addLastRowKeys = + function(keyList, is102, opt_compactKeyboardId) { + keyList.push(i18n.input.chrome.inputview.content.util.createGlobeKey()); + keyList.push(i18n.input.chrome.inputview.content.util.createMenuKey( + opt_compactKeyboardId)); + keyList.push(i18n.input.chrome.inputview.content.util.createCtrlKey()); + keyList.push(i18n.input.chrome.inputview.content.util.createAltKey()); + keyList.push(i18n.input.chrome.inputview.content.util.createSpaceKey()); + keyList.push(i18n.input.chrome.inputview.content.util.createEnSwitcherKey()); + keyList.push(i18n.input.chrome.inputview.content.util.createAltgrKey()); + keyList.push(i18n.input.chrome.inputview.content.util.createArrowKey( + i18n.input.chrome.inputview.Direction.LEFT)); + keyList.push(i18n.input.chrome.inputview.content.util.createArrowKey( + i18n.input.chrome.inputview.Direction.RIGHT)); + keyList.push(i18n.input.chrome.inputview.content.util. + createHideKeyboardKey()); +}; +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/controller.js b/third_party/google_input_tools/src/chrome/os/inputview/controller.js new file mode 100644 index 0000000..27f305b --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/controller.js @@ -0,0 +1,1658 @@ +// 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.Controller'); + +goog.require('goog.Disposable'); +goog.require('goog.array'); +goog.require('goog.async.Delay'); +goog.require('goog.dom'); +goog.require('goog.dom.classlist'); +goog.require('goog.events.EventHandler'); +goog.require('goog.events.EventType'); +goog.require('goog.i18n.bidi'); +goog.require('goog.object'); +goog.require('i18n.input.chrome.DataSource'); +goog.require('i18n.input.chrome.inputview.Adapter'); +goog.require('i18n.input.chrome.inputview.CandidatesInfo'); +goog.require('i18n.input.chrome.inputview.ConditionName'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.KeyboardContainer'); +goog.require('i18n.input.chrome.inputview.M17nModel'); +goog.require('i18n.input.chrome.inputview.Model'); +goog.require('i18n.input.chrome.inputview.PerfTracker'); +goog.require('i18n.input.chrome.inputview.ReadyState'); +goog.require('i18n.input.chrome.inputview.Settings'); +goog.require('i18n.input.chrome.inputview.SizeSpec'); +goog.require('i18n.input.chrome.inputview.SoundController'); +goog.require('i18n.input.chrome.inputview.SpecNodeName'); +goog.require('i18n.input.chrome.inputview.StateType'); +goog.require('i18n.input.chrome.inputview.Statistics'); +goog.require('i18n.input.chrome.inputview.SwipeDirection'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.inputview.elements.content.Candidate'); +goog.require('i18n.input.chrome.inputview.elements.content.CandidateView'); +goog.require('i18n.input.chrome.inputview.elements.content.ExpandedCandidateView'); +goog.require('i18n.input.chrome.inputview.elements.content.MenuView'); +goog.require('i18n.input.chrome.inputview.events.EventType'); +goog.require('i18n.input.chrome.inputview.events.KeyCodes'); +goog.require('i18n.input.chrome.inputview.handler.PointerHandler'); +goog.require('i18n.input.chrome.inputview.util'); +goog.require('i18n.input.chrome.message.ContextType'); +goog.require('i18n.input.chrome.message.Name'); +goog.require('i18n.input.chrome.message.Type'); +goog.require('i18n.input.lang.InputToolCode'); + + + +goog.scope(function() { +var CandidateType = i18n.input.chrome.inputview.elements.content.Candidate.Type; +var Candidate = i18n.input.chrome.inputview.elements.content.Candidate; +var CandidateView = i18n.input.chrome.inputview.elements.content.CandidateView; +var ConditionName = i18n.input.chrome.inputview.ConditionName; +var ContextType = i18n.input.chrome.message.ContextType; +var Css = i18n.input.chrome.inputview.Css; +var ElementType = i18n.input.chrome.inputview.elements.ElementType; +var EventType = i18n.input.chrome.inputview.events.EventType; +var ExpandedCandidateView = i18n.input.chrome.inputview.elements.content. + ExpandedCandidateView; +var InputToolCode = i18n.input.lang.InputToolCode; +var KeyCodes = i18n.input.chrome.inputview.events.KeyCodes; +var MenuView = i18n.input.chrome.inputview.elements.content.MenuView; +var Name = i18n.input.chrome.message.Name; +var PerfTracker = i18n.input.chrome.inputview.PerfTracker; +var SizeSpec = i18n.input.chrome.inputview.SizeSpec; +var SpecNodeName = i18n.input.chrome.inputview.SpecNodeName; +var StateType = i18n.input.chrome.inputview.StateType; +var content = i18n.input.chrome.inputview.elements.content; +var SoundController = i18n.input.chrome.inputview.SoundController; +var Sounds = i18n.input.chrome.inputview.Sounds; +var util = i18n.input.chrome.inputview.util; + + + +/** + * The controller of the input view keyboard. + * + * @param {string} keyset The keyboard keyset. + * @param {string} languageCode The language code for this keyboard. + * @param {string} passwordLayout The layout for password box. + * @param {string} name The input tool name. + * @constructor + * @extends {goog.Disposable} + */ +i18n.input.chrome.inputview.Controller = function(keyset, languageCode, + passwordLayout, name) { + /** + * The model. + * + * @type {!i18n.input.chrome.inputview.Model} + * @private + */ + this.model_ = new i18n.input.chrome.inputview.Model(); + + /** @private {!i18n.input.chrome.inputview.PerfTracker} */ + this.perfTracker_ = new i18n.input.chrome.inputview.PerfTracker(); + + /** + * The layout map. + * + * @type {!Object.<string, !Object>} + * @private + */ + this.layoutDataMap_ = {}; + + /** + * The keyset data map. + * + * @type {!Object.<string, !Object>} + * @private + */ + this.keysetDataMap_ = {}; + + /** + * The event handler. + * + * @type {!goog.events.EventHandler} + * @private + */ + this.handler_ = new goog.events.EventHandler(this); + + /** + * The m17n model. + * + * @type {!i18n.input.chrome.inputview.M17nModel} + * @private + */ + this.m17nModel_ = new i18n.input.chrome.inputview.M17nModel(); + + /** + * The pointer handler. + * + * @type {!i18n.input.chrome.inputview.handler.PointerHandler} + * @private + */ + this.pointerHandler_ = new i18n.input.chrome.inputview.handler. + PointerHandler(); + + /** + * The statistics object for recording metrics values. + * + * @type {!i18n.input.chrome.inputview.Statistics} + * @private + */ + this.statistics_ = i18n.input.chrome.inputview.Statistics.getInstance(); + + /** @private {!i18n.input.chrome.inputview.ReadyState} */ + this.readyState_ = new i18n.input.chrome.inputview.ReadyState(); + + /** @private {!i18n.input.chrome.inputview.Adapter} */ + this.adapter_ = new i18n.input.chrome.inputview.Adapter(this.readyState_); + + /** @private {!i18n.input.chrome.inputview.KeyboardContainer} */ + this.container_ = new i18n.input.chrome.inputview.KeyboardContainer( + this.adapter_); + this.container_.render(); + + /** @private {!i18n.input.chrome.inputview.SoundController} */ + this.soundController_ = new SoundController(false); + + /** + * The context type and keyset mapping group by input method id. + * key: input method id. + * value: Object + * key: context type string. + * value: keyset string. + * + * @private {!Object.<string, !Object.<string, string>>} + */ + this.contextTypeToKeysetMap_ = {}; + + this.initialize(keyset, languageCode, passwordLayout, name); + /** + * The suggestions. + * Note: sets a default empty result to avoid null check. + * + * @private {!i18n.input.chrome.inputview.CandidatesInfo} + */ + this.candidatesInfo_ = i18n.input.chrome.inputview.CandidatesInfo.getEmpty(); + + this.registerEventHandler_(); +}; +goog.inherits(i18n.input.chrome.inputview.Controller, + goog.Disposable); +var Controller = i18n.input.chrome.inputview.Controller; + + +/** + * @define {boolean} Flag to disable handwriting. + */ +Controller.DISABLE_HWT = false; + + +/** + * A flag to indicate whether the shift is for auto capital. + * + * @private {boolean} + */ +Controller.prototype.shiftForAutoCapital_ = false; + + +/** + * @define {boolean} Flag to indicate whether it is debugging. + */ +Controller.DEV = false; + + +/** + * The handwriting view code, use the code can switch handwriting panel view. + * + * @const {string} + * @private + */ +Controller.HANDWRITING_VIEW_CODE_ = 'hwt'; + + +/** + * The emoji view code, use the code can switch to emoji. + * + * @const {string} + * @private + */ +Controller.EMOJI_VIEW_CODE_ = 'emoji'; + + +/** + * The limitation for backspace repeat time to avoid unexpected + * problem that backspace is held all the time. + * + * @private {number} + */ +Controller.BACKSPACE_REPEAT_LIMIT_ = 255; + + +/** + * The repeated times of the backspace. + * + * @private {number} + */ +Controller.prototype.backspaceRepeated_ = 0; + + +/** + * The handwriting input tool code suffix. + * + * @const {string} + * @private + */ +Controller.HANDWRITING_CODE_SUFFIX_ = '-t-i0-handwrit'; + + +/** + * True if the settings is loaded. + * + * @type {boolean} + */ +Controller.prototype.isSettingReady = false; + + +/** + * True if the keyboard is set up. + * Note: This flag is only used for automation testing. + * + * @type {boolean} + */ +Controller.prototype.isKeyboardReady = false; + + +/** + * The auto repeat timer for backspace hold. + * + * @type {goog.async.Delay} + * @private + */ +Controller.prototype.backspaceAutoRepeat_; + + +/** + * The active keyboard code. + * + * @type {string} + * @private + */ +Controller.prototype.currentKeyset_ = ''; + + +/** + * The current input method id. + * + * @private {string} + */ +Controller.prototype.currentInputmethod_ = ''; + + +/** + * The operations on candidates. + * + * @enum {number} + */ +Controller.CandidatesOperation = { + NONE: 0, + EXPAND: 1, + SHRINK: 2 +}; + + +/** + * The active language code. + * + * @type {string} + * @private + */ +Controller.prototype.lang_; + + +/** + * The password keyset. + * + * @private {string} + */ +Controller.prototype.passwordKeyset_ = ''; + + +/** + * The soft key map, because key configuration is loaded before layout, + * controller needs this varaible to save it and hook into keyboard view. + * + * @type {!Array.<!content.SoftKey>} + * @private + */ +Controller.prototype.softKeyList_; + + +/** + * The mapping from soft key id to soft key view id. + * + * @type {!Object.<string, string>} + * @private + */ +Controller.prototype.mapping_; + + +/** + * The dead key. + * + * @type {string} + * @private + */ +Controller.prototype.deadKey_ = ''; + + +/** + * The input tool name. + * + * @type {string} + * @private + */ +Controller.prototype.title_; + + +/** + * Registers event handlers. + * @private + */ +Controller.prototype.registerEventHandler_ = function() { + this.handler_. + listen(this.model_, + EventType.LAYOUT_LOADED, + this.onLayoutLoaded_). + listen(this.model_, + EventType.CONFIG_LOADED, + this.onConfigLoaded_). + listen(this.m17nModel_, + EventType.CONFIG_LOADED, + this.onConfigLoaded_). + listen(this.pointerHandler_, [ + EventType.LONG_PRESS, + EventType.CLICK, + EventType.DOUBLE_CLICK, + EventType.DOUBLE_CLICK_END, + EventType.POINTER_UP, + EventType.POINTER_DOWN, + EventType.POINTER_OVER, + EventType.POINTER_OUT, + EventType.SWIPE + ], this.onPointerEvent_). + listen(window, goog.events.EventType.RESIZE, this.resize). + listen(this.adapter_, + i18n.input.chrome.inputview.events.EventType. + SURROUNDING_TEXT_CHANGED, + this.onSurroundingTextChanged_). + listen(this.adapter_, + i18n.input.chrome.DataSource.EventType.CANDIDATES_BACK, + this.onCandidatesBack_). + listen(this.adapter_, + i18n.input.chrome.inputview.events.EventType.CONTEXT_FOCUS, + this.onContextFocus_). + listen(this.adapter_, + i18n.input.chrome.inputview.events.EventType.CONTEXT_BLUR, + this.onContextBlur_). + listen(this.adapter_, + i18n.input.chrome.inputview.events.EventType.VISIBILITY_CHANGE, + this.onVisibilityChange_). + listen(this.adapter_, + i18n.input.chrome.inputview.events.EventType.SETTINGS_READY, + this.onSettingsReady_). + listen(this.adapter_, + i18n.input.chrome.message.Type.UPDATE_SETTINGS, + this.onUpdateSettings_); +}; + + +/** + * Callback for updating settings. + * + * @param {!i18n.input.chrome.message.Event} e . + * @private + */ +Controller.prototype.onUpdateSettings_ = function(e) { + var settings = this.model_.settings; + if (goog.isDef(e.msg['autoSpace'])) { + settings.autoSpace = e.msg['autoSpace']; + } + if (goog.isDef(e.msg['autoCapital'])) { + settings.autoCapital = e.msg['autoCapital']; + } + if (goog.isDef(e.msg['candidatesNavigation'])) { + settings.candidatesNavigation = e.msg['candidatesNavigation']; + } + if (goog.isDef(e.msg['supportCompact'])) { + settings.supportCompact = e.msg['supportCompact']; + } + if (goog.isDef(e.msg[Name.KEYSET])) { + this.contextTypeToKeysetMap_[this.currentInputMethod_][ + ContextType.DEFAULT] = e.msg[Name.KEYSET]; + } + if (goog.isDef(e.msg['enableLongPress'])) { + settings.enableLongPress = e.msg['enableLongPress']; + } + if (goog.isDef(e.msg['doubleSpacePeriod'])) { + settings.doubleSpacePeriod = e.msg['doubleSpacePeriod']; + } + if (goog.isDef(e.msg['soundOnKeypress'])) { + settings.soundOnKeypress = e.msg['soundOnKeypress']; + this.soundController_.setEnabled(settings.soundOnKeypress); + } + this.perfTracker_.tick(PerfTracker.TickName.BACKGROUND_SETTINGS_FETCHED); + var isPassword = this.adapter_.isPasswordBox(); + this.switchToKeySet(this.getActiveKeyset_()); +}; + + +/** + * Callback for setting ready. + * + * @private + */ +Controller.prototype.onSettingsReady_ = function() { + if (this.isSettingReady) { + return; + } + + this.isSettingReady = true; + var keysetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_]; + if (this.adapter_.isA11yMode) { + keysetMap[ContextType.PASSWORD] = keysetMap[ContextType.DEFAULT] = + util.getConfigName(keysetMap[ContextType.DEFAULT]); + } else { + var preferredKeyset = /** @type {string} */ (this.model_.settings. + getPreference(util.getConfigName( + keysetMap[ContextType.DEFAULT]))); + if (preferredKeyset) { + keysetMap[ContextType.PASSWORD] = keysetMap[ContextType.DEFAULT] = + preferredKeyset; + } + } + this.maybeCreateViews_(); + this.switchToKeySet(this.getActiveKeyset_()); +}; + + +/** + * Gets the data for spatial module. + * + * @param {!content.SoftKey} key . + * @param {number} x The x-offset of the touch point. + * @param {number} y The y-offset of the touch point. + * @return {!Object} . + * @private + */ +Controller.prototype.getSpatialData_ = function(key, x, y) { + var items = []; + items.push([this.getKeyContent_(key), key.estimator.estimateInLogSpace(x, y) + ]); + for (var i = 0; i < key.nearbyKeys.length; i++) { + var nearByKey = key.nearbyKeys[i]; + var content = this.getKeyContent_(nearByKey); + if (content && util.REGEX_LANGUAGE_MODEL_CHARACTERS.test(content)) { + items.push([content, nearByKey.estimator.estimateInLogSpace(x, y)]); + } + } + goog.array.sort(items, function(item1, item2) { + return item1[1] - item2[1]; + }); + var sources = items.map(function(item) { + return item[0].toLowerCase(); + }); + var possibilities = items.map(function(item) { + return item[1]; + }); + return { + 'sources': sources, + 'possibilities': possibilities + }; +}; + + +/** + * Gets the key content. + * + * @param {!content.SoftKey} key . + * @return {string} . + * @private + */ +Controller.prototype.getKeyContent_ = function(key) { + if (key.type == i18n.input.chrome.inputview.elements.ElementType. + CHARACTER_KEY) { + key = /** @type {!content.CharacterKey} */ (key); + return key.getActiveCharacter(); + } + if (key.type == i18n.input.chrome.inputview.elements.ElementType. + COMPACT_KEY) { + key = /** @type {!content.FunctionalKey} */ (key); + return key.text; + } + return ''; +}; + + +/** + * Callback for pointer event. + * + * @param {!i18n.input.chrome.inputview.events.PointerEvent} e . + * @private + */ +Controller.prototype.onPointerEvent_ = function(e) { + if ((this.adapter_.isChromeVoxOn || !this.model_.settings.enableLongPress) && + e.type == EventType.LONG_PRESS) { + return; + } + + if (e.view) { + this.handlePointerAction_(e.view, e); + } else { + var tabbableKeysets = [ + Controller.HANDWRITING_VIEW_CODE_, + Controller.EMOJI_VIEW_CODE_]; + if (goog.array.contains(tabbableKeysets, this.currentKeyset_)) { + this.resetAll_(); + this.switchToKeySet(this.container_.currentKeysetView.fromKeyset); + } + } +}; + + +/** + * Handles the swipe action. + * + * @param {!i18n.input.chrome.inputview.elements.Element} view The view, for + * swipe event, the view would be the soft key which starts the swipe. + * @param {!i18n.input.chrome.inputview.events.SwipeEvent} e The swipe event. + * @private + */ +Controller.prototype.handleSwipeAction_ = function(view, e) { + var direction = e.direction; + if (this.container_.altDataView.isVisible()) { + this.container_.altDataView.highlightItem(e.x, e.y); + return; + } + + if (view.type == ElementType.CHARACTER_KEY) { + view = /** @type {!content.CharacterKey} */ (view); + if (direction & i18n.input.chrome.inputview.SwipeDirection.UP || + direction & i18n.input.chrome.inputview.SwipeDirection.DOWN) { + var ch = view.getCharacterByGesture(!!(direction & + i18n.input.chrome.inputview.SwipeDirection.UP)); + if (ch) { + view.flickerredCharacter = ch; + } + } + } + + if (view.type == ElementType.COMPACT_KEY) { + view = /** @type {!content.CompactKey} */ (view); + if ((direction & i18n.input.chrome.inputview.SwipeDirection.UP) && + view.hintText) { + view.flickerredCharacter = view.hintText; + } + } +}; + + +/** + * Execute a command. + * + * @param {!i18n.input.chrome.inputview.elements.content.MenuView.Command} + * command The command that about to be executed. + * @param {string=} opt_arg The optional command argument. + * @private + */ +Controller.prototype.executeCommand_ = function(command, opt_arg) { + var CommandEnum = MenuView.Command; + switch (command) { + case CommandEnum.SWITCH_IME: + var inputMethodId = opt_arg; + if (inputMethodId) { + this.adapter_.switchToInputMethod(inputMethodId); + } + break; + + case CommandEnum.SWITCH_KEYSET: + var keyset = opt_arg; + if (keyset) { + this.statistics_.recordSwitch(keyset); + this.switchToKeySet(keyset); + } + break; + case CommandEnum.OPEN_EMOJI: + this.switchToKeySet(Controller.EMOJI_VIEW_CODE_); + break; + + case CommandEnum.OPEN_HANDWRITING: + // TODO: remember handwriting keyset. + this.switchToKeySet(Controller.HANDWRITING_VIEW_CODE_); + break; + + case CommandEnum.OPEN_SETTING: + if (window.inputview) { + inputview.openSettings(); + } + break; + } +}; + + +/** + * Handles the pointer action. + * + * @param {!i18n.input.chrome.inputview.elements.Element} view The view. + * @param {!i18n.input.chrome.inputview.events.PointerEvent} e . + * @private + */ +Controller.prototype.handlePointerAction_ = function(view, e) { + if (e.type == i18n.input.chrome.inputview.events.EventType.SWIPE) { + e = /** @type {!i18n.input.chrome.inputview.events.SwipeEvent} */ (e); + this.handleSwipeAction_(view, e); + } + switch (view.type) { + case ElementType.BACK_BUTTON: + if (e.type == EventType.POINTER_UP) { + this.switchToKeySet(this.container_.currentKeysetView.fromKeyset); + } + return; + case ElementType.EXPAND_CANDIDATES: + if (e.type == EventType.POINTER_UP) { + this.showCandidates_(this.candidatesInfo_.source, + this.candidatesInfo_.candidates, + Controller.CandidatesOperation.EXPAND); + } + return; + case ElementType.SHRINK_CANDIDATES: + if (e.type == EventType.POINTER_UP) { + this.showCandidates_(this.candidatesInfo_.source, + this.candidatesInfo_.candidates, + Controller.CandidatesOperation.SHRINK); + } + return; + case ElementType.CANDIDATE: + view = /** @type {!Candidate} */ (view); + if (e.type == EventType.POINTER_UP) { + if (view.candidateType == CandidateType.CANDIDATE) { + this.adapter_.selectCandidate(view.candidate); + } else if (view.candidateType == CandidateType.NUMBER) { + this.adapter_.commitText(view.candidate[Name.CANDIDATE]); + } + this.container_.cleanStroke(); + } + if (e.type == EventType.POINTER_OUT || e.type == EventType.POINTER_UP) { + view.setHighlighted(false); + } else if (e.type == EventType.POINTER_DOWN || + e.type == EventType.POINTER_OVER) { + view.setHighlighted(true); + } + return; + + case ElementType.ALTDATA_VIEW: + view = /** @type {!content.AltDataView} */ (view); + if (e.type == EventType.POINTER_DOWN && + e.target == view.getCoverElement()) { + view.hide(); + } else if (e.type == EventType.POINTER_UP) { + var ch = view.getHighlightedCharacter(); + this.adapter_.sendKeyDownAndUpEvent(ch, view.triggeredBy.id, + view.triggeredBy.keyCode, + {'sources': [ch.toLowerCase()], 'possibilities': [1]}); + view.hide(); + this.clearUnstickyState_(); + } + return; + + case ElementType.MENU_ITEM: + view = /** @type {!content.MenuItem} */ (view); + if (e.type == EventType.CLICK) { + this.resetAll_(); + this.executeCommand_.apply(this, view.getCommand()); + this.container_.menuView.hide(); + } + view.setHighlighted(e.type == EventType.POINTER_DOWN || + e.type == EventType.POINTER_OVER); + // TODO: Add chrome vox support. + return; + + case ElementType.MENU_VIEW: + view = /** @type {!MenuView} */ (view); + + if (e.type == EventType.POINTER_DOWN && + e.target == view.getCoverElement()) { + view.hide(); + } + return; + + case ElementType.EMOJI_KEY: + if (e.type == EventType.POINTER_UP) { + if (!this.container_.currentKeysetView.isDragging && view.text != '') { + this.adapter_.commitText(view.text); + } + } + return; + + case ElementType.HWT_PRIVACY_GOT_IT: + this.adapter_.sendHwtPrivacyConfirmMessage(); + return; + + case ElementType.SOFT_KEY_VIEW: + // Delegates the events on the soft key view to its soft key. + view = /** @type {!i18n.input.chrome.inputview.elements.layout. + SoftKeyView} */ (view); + if (!view.softKey) { + return; + } + view = view.softKey; + } + + if (view.type != ElementType.MODIFIER_KEY && + !this.container_.altDataView.isVisible() && + !this.container_.menuView.isVisible()) { + // The highlight of the modifier key is depending on the state instead + // of the key down or up. + if (e.type == EventType.POINTER_OVER || e.type == EventType.POINTER_DOWN || + e.type == EventType.DOUBLE_CLICK) { + view.setHighlighted(true); + } else if (e.type == EventType.POINTER_OUT || + e.type == EventType.POINTER_UP || + e.type == EventType.DOUBLE_CLICK_END) { + view.setHighlighted(false); + } + } + this.handlePointerEventForSoftKey_( + /** @type {!content.SoftKey} */ (view), e); + this.updateContextModifierState_(); +}; + + +/** + * Handles softkey of the pointer action. + * + * @param {!content.SoftKey} softKey . + * @param {!i18n.input.chrome.inputview.events.PointerEvent} e . + * @private + */ +Controller.prototype.handlePointerEventForSoftKey_ = function(softKey, e) { + var key; + switch (softKey.type) { + case ElementType.CANDIDATES_PAGE_UP: + if (e.type == EventType.POINTER_UP) { + this.container_.expandedCandidateView.pageUp(); + } + break; + case ElementType.CANDIDATES_PAGE_DOWN: + if (e.type == EventType.POINTER_UP) { + this.container_.expandedCandidateView.pageDown(); + } + break; + case ElementType.CHARACTER_KEY: + key = /** @type {!content.CharacterKey} */ (softKey); + if (e.type == EventType.LONG_PRESS) { + this.container_.altDataView.show( + key, goog.i18n.bidi.isRtlLanguage(this.languageCode_)); + } else if (e.type == EventType.POINTER_UP) { + this.model_.stateManager.triggerChording(); + var ch = key.getActiveCharacter(); + this.adapter_.sendKeyDownAndUpEvent(ch, key.id, key.keyCode, + this.getSpatialData_(key, e.x, e.y)); + this.clearUnstickyState_(); + key.flickerredCharacter = ''; + } + break; + + case ElementType.MODIFIER_KEY: + key = /** @type {!content.ModifierKey} */ (softKey); + var isStateEnabled = this.model_.stateManager.hasState(key.toState); + var isChording = this.model_.stateManager.isChording(key.toState); + if (e.type == EventType.POINTER_DOWN) { + this.changeState_(key.toState, !isStateEnabled, true); + this.model_.stateManager.setKeyDown(key.toState, true); + } else if (e.type == EventType.POINTER_UP || e.type == EventType. + POINTER_OUT) { + if (isChording) { + this.changeState_(key.toState, false, false); + } else if (key.toState != StateType.CAPSLOCK && + this.model_.stateManager.isKeyDown(key.toState)) { + this.changeState_(key.toState, isStateEnabled, false); + } + this.model_.stateManager.setKeyDown(key.toState, false); + } else if (e.type == EventType.DOUBLE_CLICK) { + this.changeState_(key.toState, isStateEnabled, true); + } else if (e.type == EventType.LONG_PRESS) { + if (!isChording) { + this.changeState_(key.toState, true, true); + this.model_.stateManager.setKeyDown(key.toState, false); + } + } + break; + + case ElementType.BACKSPACE_KEY: + key = /** @type {!content.FunctionalKey} */ (softKey); + if (e.type == EventType.POINTER_DOWN) { + this.backspaceTick_(); + } else if (e.type == EventType.POINTER_UP || e.type == EventType. + POINTER_OUT) { + this.stopBackspaceAutoRepeat_(); + this.adapter_.sendKeyUpEvent('\u0008', KeyCodes.BACKSPACE); + } + break; + + case ElementType.TAB_KEY: + key = /** @type {!content.FunctionalKey} */ (softKey); + if (e.type == EventType.POINTER_DOWN) { + this.adapter_.sendKeyDownEvent('\u0009', KeyCodes.TAB); + } else if (e.type == EventType.POINTER_UP) { + this.adapter_.sendKeyUpEvent('\u0009', KeyCodes.TAB); + } + break; + + case ElementType.ENTER_KEY: + key = /** @type {!content.FunctionalKey} */ (softKey); + if (e.type == EventType.POINTER_DOWN) { + this.adapter_.sendKeyDownEvent('\u000D', KeyCodes.ENTER); + } else if (e.type == EventType.POINTER_UP) { + this.adapter_.sendKeyUpEvent('\u000D', KeyCodes.ENTER); + } + break; + + case ElementType.ARROW_UP: + if (e.type == EventType.POINTER_DOWN) { + this.adapter_.sendKeyDownEvent('', KeyCodes.ARROW_UP); + } else if (e.type == EventType.POINTER_UP) { + this.adapter_.sendKeyUpEvent('', KeyCodes.ARROW_UP); + } + break; + + case ElementType.ARROW_DOWN: + if (e.type == EventType.POINTER_DOWN) { + this.adapter_.sendKeyDownEvent('', KeyCodes.ARROW_DOWN); + } else if (e.type == EventType.POINTER_UP) { + this.adapter_.sendKeyUpEvent('', KeyCodes.ARROW_DOWN); + } + break; + + case ElementType.ARROW_LEFT: + if (e.type == EventType.POINTER_DOWN) { + this.adapter_.sendKeyDownEvent('', KeyCodes.ARROW_LEFT); + } else if (e.type == EventType.POINTER_UP) { + this.adapter_.sendKeyUpEvent('', KeyCodes.ARROW_LEFT); + } + break; + + case ElementType.ARROW_RIGHT: + if (e.type == EventType.POINTER_DOWN) { + this.adapter_.sendKeyDownEvent('', KeyCodes.ARROW_RIGHT); + } else if (e.type == EventType.POINTER_UP) { + this.adapter_.sendKeyUpEvent('', KeyCodes.ARROW_RIGHT); + } + break; + case ElementType.EN_SWITCHER: + if (e.type == EventType.POINTER_UP) { + key = /** @type {!content.EnSwitcherKey} */ (softKey); + this.adapter_.toggleLanguageState(this.model_.stateManager.isEnMode); + this.model_.stateManager.isEnMode = !this.model_.stateManager.isEnMode; + key.update(); + } + break; + case ElementType.SPACE_KEY: + key = /** @type {!content.SpaceKey} */ (softKey); + var doubleSpacePeriod = this.model_.settings.doubleSpacePeriod; + if (e.type == EventType.POINTER_UP || (!doubleSpacePeriod && e.type == + EventType.DOUBLE_CLICK_END)) { + this.adapter_.sendKeyDownAndUpEvent(key.getCharacter(), + KeyCodes.SPACE); + this.clearUnstickyState_(); + } else if (e.type == EventType.DOUBLE_CLICK && doubleSpacePeriod) { + this.adapter_.doubleClickOnSpaceKey(); + } + break; + + case ElementType.SWITCHER_KEY: + key = /** @type {!content.SwitcherKey} */ (softKey); + if (e.type == EventType.POINTER_UP) { + this.statistics_.recordSwitch(key.toKeyset); + if (this.isSubKeyset_(key.toKeyset, this.currentKeyset_)) { + this.model_.stateManager.reset(); + this.container_.update(); + this.updateContextModifierState_(); + this.container_.menuView.hide(); + } else { + this.resetAll_(); + } + // Switch to the specific keyboard. + this.switchToKeySet(key.toKeyset); + if (key.record) { + this.model_.settings.savePreference( + util.getConfigName(key.toKeyset), + key.toKeyset); + } + } + break; + + case ElementType.COMPACT_KEY: + key = /** @type {!content.CompactKey} */ (softKey); + if (e.type == EventType.LONG_PRESS) { + this.container_.altDataView.show( + key, goog.i18n.bidi.isRtlLanguage(this.languageCode_)); + } else if (e.type == EventType.POINTER_UP) { + this.model_.stateManager.triggerChording(); + this.adapter_.sendKeyDownAndUpEvent(key.getActiveCharacter(), '', 0, + this.getSpatialData_(key, e.x, e.y)); + this.clearUnstickyState_(); + key.flickerredCharacter = ''; + } + break; + + case ElementType.HIDE_KEYBOARD_KEY: + if (e.type == EventType.POINTER_UP) { + this.adapter_.hideKeyboard(); + } + break; + + case ElementType.MENU_KEY: + key = /** @type {!content.MenuKey} */ (softKey); + if (e.type == EventType.POINTER_DOWN) { + var isCompact = this.currentKeyset_.indexOf('compact') != -1; + var remappedKeyset = this.getRemappedKeyset_(this.currentKeyset_); + var keysetData = this.keysetDataMap_[remappedKeyset]; + var enableCompact = !this.adapter_.isA11yMode && + !!keysetData[SpecNodeName.HAS_COMPACT_KEYBOARD] && + this.model_.settings.supportCompact; + var self = this; + var currentKeyset = this.currentKeyset_; + var hasHwt = !this.adapter_.isPasswordBox() && + !Controller.DISABLE_HWT && goog.object.contains( + InputToolCode, this.getHwtInputToolCode_()); + var enableSettings = this.shouldEnableSettings() && + window.inputview && inputview.openSettings; + this.adapter_.getInputMethods(function(inputMethods) { + this.container_.menuView.show(key, currentKeyset, isCompact, + enableCompact, this.currentInputMethod_, inputMethods, hasHwt, + enableSettings, this.adapter_.isExperimental); + }.bind(this)); + } + break; + + case ElementType.GLOBE_KEY: + if (e.type == EventType.POINTER_UP) { + this.adapter_.clearModifierStates(); + this.adapter_.setModifierState( + i18n.input.chrome.inputview.StateType.CTRL, true); + this.adapter_.sendKeyDownAndUpEvent(' ', KeyCodes.SPACE, 0x20); + this.adapter_.setModifierState( + i18n.input.chrome.inputview.StateType.CTRL, false); + } + break; + case ElementType.IME_SWITCH: + key = /** @type {!content.FunctionalKey} */ (softKey); + this.adapter_.sendKeyDownAndUpEvent('', key.id); + break; + } + // Play key sound on pointer up. + if (e.type == EventType.POINTER_UP) + this.soundController_.onKeyUp(softKey.type); +}; + + +/** + * Clears unsticky state. + * + * @private + */ +Controller.prototype.clearUnstickyState_ = function() { + if (this.model_.stateManager.hasUnStickyState()) { + for (var key in StateType) { + var value = StateType[key]; + if (this.model_.stateManager.hasState(value) && + !this.model_.stateManager.isSticky(value)) { + this.changeState_(value, false, false); + } + } + } + this.container_.update(); +}; + + +/** + * Stops the auto-repeat for backspace. + * + * @private + */ +Controller.prototype.stopBackspaceAutoRepeat_ = function() { + goog.dispose(this.backspaceAutoRepeat_); + this.backspaceAutoRepeat_ = null; + this.adapter_.sendKeyUpEvent('\u0008', KeyCodes.BACKSPACE); + this.backspaceRepeated_ = 0; +}; + + +/** + * The tick for the backspace key. + * + * @private + */ +Controller.prototype.backspaceTick_ = function() { + if (this.backspaceRepeated_ >= Controller.BACKSPACE_REPEAT_LIMIT_) { + this.stopBackspaceAutoRepeat_(); + return; + } + this.backspaceRepeated_++; + this.backspaceDown_(); + this.soundController_.onKeyRepeat(ElementType.BACKSPACE_KEY); + + if (this.backspaceAutoRepeat_) { + this.backspaceAutoRepeat_.start(75); + } else { + this.backspaceAutoRepeat_ = new goog.async.Delay( + goog.bind(this.backspaceTick_, this), 300); + this.backspaceAutoRepeat_.start(); + } +}; + + +/** + * Callback for VISIBILITY_CHANGE. + * + * @private + */ +Controller.prototype.onVisibilityChange_ = function() { + if (!this.adapter_.isVisible) { + this.resetAll_(); + } +}; + + +/** + * Resets the whole keyboard include clearing candidates, + * reset modifier state, etc. + * + * @private + */ +Controller.prototype.resetAll_ = function() { + this.clearCandidates_(); + this.container_.candidateView.hideNumberRow(); + this.model_.stateManager.reset(); + this.container_.update(); + this.updateContextModifierState_(); + this.deadKey_ = ''; + this.resize(); + this.container_.expandedCandidateView.close(); + this.container_.menuView.hide(); +}; + + +/** + * Callback when the context is changed. + * + * @private + */ +Controller.prototype.onContextFocus_ = function() { + this.resetAll_(); + this.switchToKeySet(this.getActiveKeyset_()); +}; + + +/** + * Callback when surrounding text is changed. + * + * @param {!i18n.input.chrome.inputview.events.SurroundingTextChangedEvent} e . + * @private + */ +Controller.prototype.onSurroundingTextChanged_ = function(e) { + if (!this.model_.settings.autoCapital || !e.text) { + return; + } + + var isShiftEnabled = this.model_.stateManager.hasState(StateType.SHIFT); + var needAutoCap = this.model_.settings.autoCapital && + util.needAutoCap(e.text); + if (needAutoCap && !isShiftEnabled) { + this.changeState_(StateType.SHIFT, true, false); + this.shiftForAutoCapital_ = true; + } else if (!needAutoCap && this.shiftForAutoCapital_) { + this.changeState_(StateType.SHIFT, false, false); + } +}; + + +/** + * Callback for Context blurs. + * + * @private + */ +Controller.prototype.onContextBlur_ = function() { + this.clearCandidates_(); + this.deadKey_ = ''; + this.container_.menuView.hide(); +}; + + +/** + * Backspace key is down. + * + * @private + */ +Controller.prototype.backspaceDown_ = function() { + if (this.container_.hasStrokesOnCanvas()) { + this.clearCandidates_(); + this.container_.cleanStroke(); + } else { + this.adapter_.sendKeyDownEvent('\u0008', KeyCodes.BACKSPACE); + } +}; + + +/** + * Callback for state change. + * + * @param {StateType} stateType The state type. + * @param {boolean} enable True to enable the state. + * @param {boolean} isSticky True to make the state sticky. + * @private + */ +Controller.prototype.changeState_ = function(stateType, enable, isSticky) { + if (stateType == StateType.ALTGR) { + var code = KeyCodes.ALT_RIGHT; + if (enable) { + this.adapter_.sendKeyDownEvent('', code); + } else { + this.adapter_.sendKeyUpEvent('', code); + } + } + if (stateType == StateType.SHIFT) { + this.shiftForAutoCapital_ = false; + } + var isEnabledBefore = this.model_.stateManager.hasState(stateType); + var isStickyBefore = this.model_.stateManager.isSticky(stateType); + this.model_.stateManager.setState(stateType, enable); + this.model_.stateManager.setSticky(stateType, isSticky); + if (isEnabledBefore != enable || isStickyBefore != isSticky) { + this.container_.update(); + } +}; + + +/** + * Updates the modifier state for context. + * + * @private + */ +Controller.prototype.updateContextModifierState_ = function() { + var stateManager = this.model_.stateManager; + this.adapter_.setModifierState(StateType.ALT, + stateManager.hasState(StateType.ALT)); + this.adapter_.setModifierState(StateType.CTRL, + stateManager.hasState(StateType.CTRL)); + this.adapter_.setModifierState(StateType.CAPSLOCK, + stateManager.hasState(StateType.CAPSLOCK)); + if (!this.shiftForAutoCapital_) { + // If shift key is automatically on because of feature - autoCapital, + // Don't set modifier state to adapter. + this.adapter_.setModifierState(StateType.SHIFT, + stateManager.hasState(StateType.SHIFT)); + } +}; + + +/** + * Callback for AUTO-COMPLETE event. + * + * @param {!i18n.input.chrome.DataSource.CandidatesBackEvent} e . + * @private + */ +Controller.prototype.onCandidatesBack_ = function(e) { + this.candidatesInfo_ = new i18n.input.chrome.inputview.CandidatesInfo( + e.source, e.candidates); + this.showCandidates_(e.source, e.candidates, Controller.CandidatesOperation. + NONE); +}; + + +/** + * Shows the candidates to the candidate view. + * + * @param {string} source The source text. + * @param {!Array.<!Object>} candidates The candidate text list. + * @param {Controller.CandidatesOperation} operation . + * @private + */ +Controller.prototype.showCandidates_ = function(source, candidates, + operation) { + var state = !!source ? ExpandedCandidateView.State.COMPLETION_CORRECTION : + ExpandedCandidateView.State.PREDICTION; + var expandView = this.container_.expandedCandidateView; + var expand = false; + if (operation == Controller.CandidatesOperation.NONE) { + expand = expandView.state == state; + } else { + expand = operation == Controller.CandidatesOperation.EXPAND; + } + + if (candidates.length == 0) { + this.clearCandidates_(); + expandView.state = ExpandedCandidateView.State.NONE; + return; + } + + // The compact pinyin needs full candidates instead of three candidates. + var isThreeCandidates = this.currentKeyset_.indexOf('compact') != -1 && + this.currentKeyset_.indexOf('pinyin-zh-CN') == -1; + if (isThreeCandidates) { + if (candidates.length > 1) { + // Swap the first candidate and the second candidate. + var tmp = candidates[0]; + candidates[0] = candidates[1]; + candidates[1] = tmp; + } + } + var isHwt = Controller.HANDWRITING_VIEW_CODE_ == this.currentKeyset_; + this.container_.candidateView.showCandidates(candidates, isThreeCandidates, + this.model_.settings.candidatesNavigation && !isHwt); + + if (expand) { + expandView.state = state; + this.container_.currentKeysetView.setVisible(false); + expandView.showCandidates(candidates, + this.container_.candidateView.candidateCount); + this.container_.candidateView.switchToIcon(CandidateView.IconType. + SHRINK_CANDIDATES, true); + } else { + expandView.state = ExpandedCandidateView.State.NONE; + expandView.setVisible(false); + this.container_.currentKeysetView.setVisible(true); + } +}; + + +/** + * Clears candidates. + * + * @private + */ +Controller.prototype.clearCandidates_ = function() { + this.candidatesInfo_ = i18n.input.chrome.inputview.CandidatesInfo.getEmpty(); + this.container_.candidateView.clearCandidates(); + this.container_.expandedCandidateView.close(); + this.container_.expandedCandidateView.state = ExpandedCandidateView.State. + NONE; + if (this.container_.currentKeysetView) { + this.container_.currentKeysetView.setVisible(true); + } + this.container_.candidateView.switchToIcon(CandidateView.IconType.BACK, + Controller.HANDWRITING_VIEW_CODE_ == this.currentKeyset_); +}; + + +/** + * Callback when the layout is loaded. + * + * @param {!i18n.input.chrome.inputview.events.LayoutLoadedEvent} e The event. + * @private + */ +Controller.prototype.onLayoutLoaded_ = function(e) { + var layoutID = e.data['layoutID']; + this.layoutDataMap_[layoutID] = e.data; + this.perfTracker_.tick(PerfTracker.TickName.LAYOUT_LOADED); + this.maybeCreateViews_(); +}; + + +/** + * Creates the whole view. + * + * @private + */ +Controller.prototype.maybeCreateViews_ = function() { + if (!this.isSettingReady) { + return; + } + + for (var keyset in this.keysetDataMap_) { + var keysetData = this.keysetDataMap_[keyset]; + var layoutId = keysetData[SpecNodeName.LAYOUT]; + var layoutData = this.layoutDataMap_[layoutId]; + if (!this.container_.keysetViewMap[keyset] && layoutData) { + var conditions = {}; + conditions[ConditionName.SHOW_ALTGR] = + keysetData[SpecNodeName.HAS_ALTGR_KEY]; + + conditions[ConditionName.SHOW_MENU] = + keysetData[SpecNodeName.SHOW_MENU_KEY]; + // In symbol and more keysets, we want to show a symbol key in the globe + // SoftKeyView. So this view should alway visible in the two keysets. + // Currently, SHOW_MENU_KEY is false for the two keysets, so we use + // !keysetData[SpecNodeName.SHOW_MENU_KEY] here. + conditions[ConditionName.SHOW_GLOBE_OR_SYMBOL] = + !keysetData[SpecNodeName.SHOW_MENU_KEY] || + this.adapter_.showGlobeKey; + conditions[ConditionName.SHOW_EN_SWITCHER_KEY] = false; + + // If the view for the keyboard code doesn't exist, and the layout + // data is ready, then creates the view. + this.container_.addKeysetView(keysetData, layoutData, keyset, + this.languageCode_, this.model_, this.title_, conditions); + this.perfTracker_.tick(PerfTracker.TickName.KEYBOARD_CREATED); + } + } + this.switchToKeySet(this.getActiveKeyset_()); +}; + + +/** + * Switch to a specific keyboard. + * + * @param {string} keyset The keyset name. + */ +Controller.prototype.switchToKeySet = function(keyset) { + if (!this.isSettingReady) { + return; + } + + var lastKeysetView = this.container_.currentKeysetView; + var ret = this.container_.switchToKeyset(this.getRemappedKeyset_(keyset), + this.title_, this.adapter_.isPasswordBox(), this.adapter_.isA11yMode, + keyset, this.currentKeyset_, this.languageCode_); + + // Update the keyset of current context type. + this.contextTypeToKeysetMap_[this.currentInputMethod_][ + this.adapter_.getContextType()] = keyset; + + if (ret) { + this.updateLanguageState_(this.currentKeyset_, keyset); + // Deactivate the last keyset view instance. + if (lastKeysetView) { + lastKeysetView.deactivate(this.currentKeyset_); + } + this.currentKeyset_ = keyset; + + this.resize(Controller.DEV); + this.statistics_.setCurrentLayout(keyset); + // Activate the current key set view instance. + this.container_.currentKeysetView.activate(keyset); + this.perfTracker_.tick(PerfTracker.TickName.KEYBOARD_SHOWN); + this.perfTracker_.stop(); + } else { + this.loadResource_(keyset); + } +}; + + +/** + * Callback when the configuration is loaded. + * + * @param {!i18n.input.chrome.inputview.events.ConfigLoadedEvent} e The event. + * @private + */ +Controller.prototype.onConfigLoaded_ = function(e) { + var data = e.data; + var keyboardCode = data[i18n.input.chrome.inputview.SpecNodeName.ID]; + this.keysetDataMap_[keyboardCode] = data; + this.perfTracker_.tick(PerfTracker.TickName.KEYSET_LOADED); + var context = data[i18n.input.chrome.inputview.SpecNodeName.ON_CONTEXT]; + if (context && !this.adapter_.isA11yMode) { + var keySetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_]; + if (!keySetMap) { + keySetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_] = {}; + } + keySetMap[context] = keyboardCode; + } + + var layoutId = data[i18n.input.chrome.inputview.SpecNodeName.LAYOUT]; + var layoutData = this.layoutDataMap_[layoutId]; + if (layoutData) { + this.maybeCreateViews_(); + } else { + this.model_.loadLayout(data[i18n.input.chrome.inputview.SpecNodeName. + LAYOUT]); + } +}; + + +/** + * Resizes the whole UI. + * + * @param {boolean=} opt_ignoreWindowResize . + */ +Controller.prototype.resize = function(opt_ignoreWindowResize) { + var height; + var widthPercent; + var candidateViewHeight; + var isHorizontal = screen.width > screen.height; + var isWideScreen = (Math.min(screen.width, screen.height) / Math.max( + screen.width, screen.height)) < 0.6; + this.model_.stateManager.covariance.update(isWideScreen, isHorizontal, + this.adapter_.isA11yMode); + if (this.adapter_.isA11yMode) { + height = SizeSpec.A11Y_HEIGHT; + widthPercent = screen.width > screen.height ? SizeSpec.A11Y_WIDTH_PERCENT. + HORIZONTAL : SizeSpec.A11Y_WIDTH_PERCENT.VERTICAL; + candidateViewHeight = SizeSpec.A11Y_CANDIDATE_VIEW_HEIGHT; + } else { + var keyset = this.keysetDataMap_[this.currentKeyset_]; + var layout = keyset && keyset[SpecNodeName.LAYOUT]; + var data = layout && this.layoutDataMap_[layout]; + var spec = data && data[SpecNodeName.WIDTH_PERCENT] || + SizeSpec.NON_A11Y_WIDTH_PERCENT; + height = SizeSpec.NON_A11Y_HEIGHT; + if (isHorizontal) { + if (isWideScreen) { + widthPercent = spec.HORIZONTAL_WIDE_SCREEN; + } else { + widthPercent = spec.HORIZONTAL; + } + } else { + widthPercent = spec.VERTICAL; + } + candidateViewHeight = SizeSpec.NON_A11Y_CANDIDATE_VIEW_HEIGHT; + } + + var viewportSize = goog.dom.getViewportSize(); + if (viewportSize.height != height && !opt_ignoreWindowResize) { + window.resizeTo(screen.width, height); + return; + } + + this.container_.resize(screen.width, height, widthPercent, + candidateViewHeight); + if (this.container_.currentKeysetView) { + this.isKeyboardReady = true; + } +}; + + +/** + * Loads the resources, for currentKeyset, passwdKeyset, handwriting, + * emoji, etc. + * + * @private + */ +Controller.prototype.loadAllResources_ = function() { + var keysetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_]; + goog.array.forEach([keysetMap[ContextType.DEFAULT], + Controller.HANDWRITING_VIEW_CODE_, + Controller.EMOJI_VIEW_CODE_, + keysetMap[ContextType.PASSWORD]], function(keyset) { + this.loadResource_(keyset); + }, this); +}; + + +/** + * Gets the remapped keyset. + * + * @param {string} keyset . + * @return {string} The remapped keyset. + * @private + */ +Controller.prototype.getRemappedKeyset_ = function(keyset) { + if (goog.array.contains(util.KEYSETS_USE_US, keyset)) { + return 'us'; + } + return keyset; +}; + + +/** + * Loads a single resource. + * + * @param {string} keyset . + * loaded. + * @private + */ +Controller.prototype.loadResource_ = function(keyset) { + var remapped = this.getRemappedKeyset_(keyset); + if (!this.keysetDataMap_[remapped]) { + if (/^m17n:/.test(remapped)) { + this.m17nModel_.loadConfig(remapped); + } else { + this.model_.loadConfig(remapped); + } + return; + } + + var layoutId = this.keysetDataMap_[remapped][SpecNodeName.LAYOUT]; + if (!this.layoutDataMap_[layoutId]) { + this.model_.loadLayout(layoutId); + return; + } +}; + + +/** + * Sets the keyboard. + * + * @param {string} keyset The keyboard keyset. + * @param {string} languageCode The language code for this keyboard. + * @param {string} passwordLayout The layout for password box. + * @param {string} title The title for this keyboard. + */ +Controller.prototype.initialize = function(keyset, languageCode, passwordLayout, + title) { + this.perfTracker_.restart(); + this.adapter_.getCurrentInputMethod(function(currentInputMethod) { + this.languageCode_ = languageCode; + this.currentInputMethod_ = currentInputMethod; + var keySetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_]; + if (!keySetMap) { + keySetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_] = {}; + } + keySetMap[ContextType.PASSWORD] = passwordLayout; + keySetMap[ContextType.DEFAULT] = keyset; + + this.title_ = title; + this.isSettingReady = false; + this.model_.settings = new i18n.input.chrome.inputview.Settings(); + this.adapter_.initialize(languageCode ? languageCode.split('-')[0] : ''); + this.loadAllResources_(); + this.switchToKeySet(this.getActiveKeyset_()); + + // Set language attribute and font of body. + document.body.setAttribute('lang', this.languageCode_); + goog.dom.classlist.add(document.body, Css.FONT); + }.bind(this)); +}; + + +/** @override */ +Controller.prototype.disposeInternal = function() { + goog.dispose(this.container_); + goog.dispose(this.adapter_); + goog.dispose(this.handler_); + goog.dispose(this.soundController_); + + goog.base(this, 'disposeInternal'); +}; + + +/** + * Gets the handwriting Input Tool code of current language code. + * + * @return {string} The handwriting Input Tool code. + * @private + */ +Controller.prototype.getHwtInputToolCode_ = function() { + return this.languageCode_.split(/_|-/)[0] + + Controller.HANDWRITING_CODE_SUFFIX_; +}; + + +/** + * True to enable settings link. + * + * @return {boolean} . + */ +Controller.prototype.shouldEnableSettings = function() { + return !this.adapter_.screen || this.adapter_.screen == 'normal'; +}; + + +/** + * Gets the active keyset, if there is a keyset to switch, return it. + * otherwise if it's a password box, return the password keyset, + * otherwise return the current keyset. + * + * @return {string} . + * @private + */ +Controller.prototype.getActiveKeyset_ = function() { + var keySetMap = this.contextTypeToKeysetMap_[this.currentInputMethod_]; + return keySetMap[this.adapter_.getContextType()] || + keySetMap[ContextType.DEFAULT]; +}; + + +/** + * True if keysetB is the sub keyset of keysetA. + * + * @param {string} keysetA . + * @param {string} keysetB . + * @return {boolean} . + * @private + */ +Controller.prototype.isSubKeyset_ = function(keysetA, keysetB) { + var segmentsA = keysetA.split('.'); + var segmentsB = keysetB.split('.'); + return segmentsA.length >= 2 && segmentsB.length >= 2 && + segmentsA[0] == segmentsB[0] && segmentsA[1] == segmentsB[1]; +}; + + +/** + * Updates the compact pinyin to set the inputcode for english and pinyin. + * + * @param {string} fromRawKeyset . + * @param {string} toRawKeyset . + * @private + */ +Controller.prototype.updateLanguageState_ = + function(fromRawKeyset, toRawKeyset) { + if (fromRawKeyset == 'pinyin-zh-CN.en.compact.qwerty' && + toRawKeyset.indexOf('en.compact') == -1) { + this.adapter_.toggleLanguageState(true); + } else if (fromRawKeyset.indexOf('en.compact') == -1 && + toRawKeyset == 'pinyin-zh-CN.en.compact.qwerty') { + this.adapter_.toggleLanguageState(false); + } else if (goog.array.contains( + i18n.input.chrome.inputview.util.KEYSETS_HAVE_EN_SWTICHER, + toRawKeyset)) { + this.adapter_.toggleLanguageState(true); + this.model_.stateManager.isEnMode = false; + this.container_.currentKeysetView.update(); + } +}; +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/covariance.js b/third_party/google_input_tools/src/chrome/os/inputview/covariance.js new file mode 100644 index 0000000..facf5c1 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/covariance.js @@ -0,0 +1,111 @@ +// 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.Covariance'); + +goog.require('i18n.input.chrome.inputview.elements.ElementType'); + + +goog.scope(function() { +var ElementType = i18n.input.chrome.inputview.elements.ElementType; + + + +/** + * The covariance used for gaussian model. + * + * @constructor + */ +i18n.input.chrome.inputview.Covariance = function() { + /** @private {number} */ + this.breakDown_ = 0; +}; +var Covariance = i18n.input.chrome.inputview.Covariance; + + +/** + * The break-down for covariance. + * + * @enum {number} + */ +Covariance.BreakDown = { + A11Y: 1, + HORIZONTAL: 2, + WIDE_SCREEN: 4 +}; + + +/** + * The key type. + * + * @type {!Object.<ElementType, number>} + */ +Covariance.ElementTypeMap = goog.object.create( + ElementType.CHARACTER_KEY, 0, + ElementType.COMPACT_KEY, 1 +); + + +/** + * The value. + * Key: the break down value. + * Value: A list - first is the covariance for full keyboard, second is for + * compact. + * + * @private {!Object.<!Array.<number>>} + */ +Covariance.VALUE_ = { + 0: [120, 160], + 1: [130, 0], + 2: [235, 342], + 3: [162, 0], + 4: [160, 213], + 5: [142, 0], + 6: [230, 332], + 7: [162, 0] +}; + + +/** + * Updates the covariance. + * + * @param {boolean} isWideScreen . + * @param {boolean} isHorizontal . + * @param {boolean} isA11y . + */ +Covariance.prototype.update = function(isWideScreen, isHorizontal, isA11y) { + this.breakDown_ = 0; + if (isWideScreen) { + this.breakDown_ |= Covariance.BreakDown.WIDE_SCREEN; + } + if (isHorizontal) { + this.breakDown_ |= Covariance.BreakDown.HORIZONTAL; + } + if (isA11y) { + this.breakDown_ |= Covariance.BreakDown.A11Y; + } +}; + + +/** + * Gets the covariance value. + * + * @param {ElementType} type . + */ +Covariance.prototype.getValue = function(type) { + var index = Covariance.ElementTypeMap[type]; + return Covariance.VALUE_[this.breakDown_][index]; +}; + +}); // goog.scope + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/css.js b/third_party/google_input_tools/src/chrome/os/inputview/css.js new file mode 100644 index 0000000..bc96129 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/css.js @@ -0,0 +1,164 @@ +// 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.Css'); + + +/** + * The css used for input view keyboard. + * + * @enum {string} + */ +i18n.input.chrome.inputview.Css = { + A11Y: goog.getCssName('inputview-a11y'), + ALTDATA_COVER: goog.getCssName('inputview-altdata-cover'), + ALTDATA_KEY: goog.getCssName('inputview-altdata-key'), + ALTDATA_SEPARATOR: goog.getCssName('inputview-altdata-separator'), + ALTDATA_VIEW: goog.getCssName('inputview-altdata-view'), + ALTGR_CONTENT: goog.getCssName('inputview-ac'), + ARROW_KEY: goog.getCssName('inputview-arrow-key'), + BACKSPACE_ICON: goog.getCssName('inputview-backspace-icon'), + CANDIDATE: goog.getCssName('inputview-candidate'), + CANDIDATES_LINE: goog.getCssName('inputview-candidates-line'), + CANDIDATES_TOP_LINE: goog.getCssName('inputview-candidates-top-line'), + CANDIDATE_AUTOCORRECT: goog.getCssName('inputview-candidate-autocorrect'), + CANDIDATE_BUTTON: goog.getCssName('inputview-candidate-button'), + CANDIDATE_DEFAULT: goog.getCssName('inputview-candidate-default'), + CANDIDATE_HIGHLIGHT: goog.getCssName('inputview-candidate-highlight'), + CANDIDATE_INTER_CONTAINER: goog.getCssName('inputview-candiate-ic'), + CANDIDATE_SEPARATOR: goog.getCssName('inputview-candidate-separator'), + CANDIDATE_VIEW: goog.getCssName('inputview-candidate-view'), + CANVAS: goog.getCssName('inputview-canvas'), + CANVAS_LEFT_COLUMN: goog.getCssName('inputview-canvas-left-column'), + CANVAS_RIGHT_COLUMN: goog.getCssName('inputview-canvas-right-column'), + CANVAS_VIEW: goog.getCssName('inputview-canvasview'), + CAPSLOCK_DOT: goog.getCssName('inputview-capslock-dot'), + CAPSLOCK_DOT_HIGHLIGHT: goog.getCssName('inputview-capslock-dot-highlight'), + CHARACTER: goog.getCssName('inputview-character'), + CHARACTER_HIGHLIGHT: goog.getCssName('inputview-ch'), + CHECKED_MENU_LIST: goog.getCssName('inputview-checked-menu-list'), + COMPACT_KEY: goog.getCssName('inputview-compact-key'), + COMPACT_SWITCHER: goog.getCssName('inputview-compact-switcher'), + CONTAINER: goog.getCssName('inputview-container'), + DEFAULT_CONTENT: goog.getCssName('inputview-dc'), + DIGIT_CHARACTER: goog.getCssName('inputview-digit-character'), + DOWN_KEY: goog.getCssName('inputview-down-key'), + ELEMENT_HIGHLIGHT: goog.getCssName('inputview-element-highlight'), + EMOJI: goog.getCssName('inputview-emoji'), + EMOJI_BACK: goog.getCssName('inputview-emoji-back'), + EMOJI_FONT: goog.getCssName('inputview-emoji-font'), + EMOJI_KEY: goog.getCssName('inputview-emoji-key'), + EMOJI_KEY_HIGHLIGHT: goog.getCssName('inputview-emoji-key-highlight'), + EMOJI_SWITCH: goog.getCssName('inputview-emoji-switch'), + EMOJI_SWITCH_CAR: + goog.getCssName('inputview-emoji-switch-car'), + EMOJI_SWITCH_EMOJI: + goog.getCssName('inputview-emoji-switch-emoji'), + EMOJI_SWITCH_EMOTICON: + goog.getCssName('inputview-emoji-switch-emoticon'), + EMOJI_SWITCH_FAVORITS: + goog.getCssName('inputview-emoji-switch-favorits'), + EMOJI_SWITCH_FLOWER: + goog.getCssName('inputview-emoji-switch-flower'), + EMOJI_SWITCH_HIGHLIGHT: + goog.getCssName('inputview-emoji-switch-highlight'), + EMOJI_SWITCH_RECENT: + goog.getCssName('inputview-emoji-switch-recent'), + EMOJI_SWITCH_SPECIAL: + goog.getCssName('inputview-emoji-switch-special'), + EMOJI_SWITCH_SYMBOL: + goog.getCssName('inputview-emoji-switch-symbol'), + EMOJI_SWITCH_TRIANGLE: + goog.getCssName('inputview-emoji-switch-triangle'), + EMOJI_TABBAR_KEY: goog.getCssName('inputview-emoji-tabbar-key'), + EMOJI_TABBAR_KEY_HIGHLIGHT: + goog.getCssName('inputview-emoji-tabbar-key-highlight'), + EMOJI_TABBAR_SK: goog.getCssName('inputview-emoji-tabbar-sk'), + EMOJI_TEXT: goog.getCssName('inputview-emoji-text'), + ENTER_ICON: goog.getCssName('inputview-enter-icon'), + EN_SWITCHER_DEFAULT: goog.getCssName('inputview-en-switcher-default'), + EN_SWITCHER_ENGLISH: goog.getCssName('inputview-en-switcher-english'), + EXPAND_CANDIDATES_ICON: goog.getCssName('inputview-expand-candidates-icon'), + EXTENDED_LAYOUT_TRANSITION: goog.getCssName('inputview-extended-transition'), + FONT: goog.getCssName('inputview-font'), + GLOBE_ICON: goog.getCssName('inputview-globe-icon'), + HANDWRITING: goog.getCssName('inputview-handwriting'), + HANDWRITING_BACK: goog.getCssName('inputview-handwriting-back'), + HANDWRITING_LAYOUT: goog.getCssName('inputview-handwriting-layout'), + HANDWRITING_NETWORK_ERROR: + goog.getCssName('inputview-handwriting-network-error'), + HANDWRITING_SWITCHER: goog.getCssName('inputview-handwriting-switcher'), + HANDWRITING_PRIVACY_COVER: + goog.getCssName('inputview-handwriting-privacy-cover'), + HANDWRITING_PRIVACY_INFO: + goog.getCssName('inputview-handwriting-privacy-info'), + HANDWRITING_PRIVACY_INFO_HIDDEN: + goog.getCssName('inputview-handwriting-privacy-info-hidden'), + HIDE_KEYBOARD_ICON: goog.getCssName('inputview-hide-keyboard-icon'), + HINT_TEXT: goog.getCssName('inputview-hint-text'), + HOLD: goog.getCssName('inputview-hold'), + IME_LIST_CONTAINER: goog.getCssName('inputview-ime-list-container'), + INDICATOR: goog.getCssName('inputview-indicator'), + INDICATOR_BACKGROUND: goog.getCssName('inputview-indicator-background'), + INLINE_DIV: goog.getCssName('inputview-inline-div'), + JP_IME_SWITCH: goog.getCssName('inputview-jp-ime-switch'), + KEY_HOLD: goog.getCssName('inputview-key-hold'), + LAYOUT_VIEW: goog.getCssName('inputview-layoutview'), + LEFT_KEY: goog.getCssName('inputview-left-key'), + LINEAR_LAYOUT: goog.getCssName('inputview-linear'), + LINEAR_LAYOUT_BORDER: goog.getCssName('inputview-linear-border'), + MENU_LIST_CHECK_MARK: goog.getCssName('inputview-menu-list-check-mark'), + MENU_FOOTER: goog.getCssName('inputview-menu-footer'), + MENU_FOOTER_EMOJI_BUTTON: + goog.getCssName('inputview-menu-footer-emoji-button'), + MENU_FOOTER_HANDWRITING_BUTTON: + goog.getCssName('inputview-menu-footer-handwriting-button'), + MENU_FOOTER_ITEM: goog.getCssName('inputview-menu-footer-item'), + MENU_FOOTER_SETTING_BUTTON: + goog.getCssName('inputview-menu-footer-setting-button'), + MENU_ICON: goog.getCssName('inputview-menu-icon'), + MENU_LIST_INDICATOR: goog.getCssName('inputview-menu-list-indicator'), + MENU_LIST_INDICATOR_NAME: + goog.getCssName('inputview-menu-list-indicator-name'), + MENU_LIST_ITEM: goog.getCssName('inputview-menu-list-item'), + MENU_LIST_NAME: goog.getCssName('inputview-menu-list-name'), + MENU_VIEW: goog.getCssName('inputview-menu-view'), + MODIFIER: goog.getCssName('inputview-modifier'), + MODIFIER_ON: goog.getCssName('inputview-modifier-on'), + MODIFIER_STATE_ICON: goog.getCssName('inputview-modifier-state-icon'), + PAGE_DOWN_ICON: goog.getCssName('inputview-page-down-icon'), + PAGE_UP_ICON: goog.getCssName('inputview-page-up-icon'), + PINYIN: goog.getCssName('inputview-pinyin'), + REGULAR_SWITCHER: goog.getCssName('inputview-regular-switcher'), + RIGHT_KEY: goog.getCssName('inputview-right-key'), + SHIFT_ICON: goog.getCssName('inputview-shift-icon'), + SHRINK_CANDIDATES_ICON: goog.getCssName('inputview-shrink-candidates-icon'), + SOFT_KEY: goog.getCssName('inputview-sk'), + SOFT_KEY_VIEW: goog.getCssName('inputview-skv'), + SPACE_ICON: goog.getCssName('inputview-space-icon'), + SPECIAL_KEY_BG: goog.getCssName('inputview-special-key-bg'), + SPECIAL_KEY_HIGHLIGHT: goog.getCssName('inputview-special-key-highlight'), + SPECIAL_KEY_NAME: goog.getCssName('inputview-special-key-name'), + SWITCHER_CHINESE: goog.getCssName('inputview-switcher-chinese'), + SWITCHER_ENGLISH: goog.getCssName('inputview-switcher-english'), + TABLE_CELL: goog.getCssName('inputview-table-cell'), + TAB_ICON: goog.getCssName('inputview-tab-icon'), + THREE_CANDIDATES: goog.getCssName('inputview-three-candidates'), + TITLE: goog.getCssName('inputview-title'), + TITLE_BAR: goog.getCssName('inputview-title-bar'), + UP_KEY: goog.getCssName('inputview-up-key'), + VERTICAL_LAYOUT: goog.getCssName('inputview-vertical'), + VIEW: goog.getCssName('inputview-view'), + WRAPPER: goog.getCssName('inputview-wrapper') +}; + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/direction.js b/third_party/google_input_tools/src/chrome/os/inputview/direction.js new file mode 100644 index 0000000..4d07482 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/direction.js @@ -0,0 +1,28 @@ +// 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.Direction'); + + +/** + * The direction. + * + * @enum {number} + */ +i18n.input.chrome.inputview.Direction = { + UP: 0, + DOWN: 1, + LEFT: 2, + RIGHT: 3 +}; + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/dom.js b/third_party/google_input_tools/src/chrome/os/inputview/dom.js new file mode 100644 index 0000000..69ab55e --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/dom.js @@ -0,0 +1,229 @@ +// Copyright 2014 The Cloud Input Tools Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provides common operation of dom for input tools. + */ + + +goog.provide('i18n.input.common.dom'); + +goog.require('goog.array'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('goog.style'); +goog.require('goog.uri.utils'); +goog.require('i18n.input.common.GlobalSettings'); + + +/** + * When detects whether the same domain iframe, browser will throw + * exceptions on accessing the cross domain iframe. Stores result to avoid to + * throws exception twice. + * Key is document uid, value is object. ifrmae uid : true/false + * + * @type {!Object.<number, !Object.<number, boolean>>} + * @private + */ +i18n.input.common.dom.sameDomainIframes_ = {}; + + +/** + * Checks the given element whether is editable. + * + * @param {!Element} element The element. + * @return {boolean} Whether the give element is editable. + */ +i18n.input.common.dom.isEditable = function(element) { + if (!element.tagName) { + return false; + } + + if (element.readOnly) { + return false; + } + + switch (element.tagName.toUpperCase()) { + case 'TEXTAREA': + return true; + case 'INPUT': + return (element.type.toUpperCase() == 'TEXT' || + element.type.toUpperCase() == 'SEARCH'); + case 'DIV': + return element.isContentEditable; + case 'IFRAME': + // Accessing iframe's contents or properties throws exception when the + // iframe is not hosted on the same domain. + // When it happens, ignore it and consider this iframe isn't editable. + /** @preserveTry */ + try { + var ifdoc = i18n.input.common.dom.getSameDomainFrameDoc(element); + return !!ifdoc && (ifdoc.designMode && + ifdoc.designMode.toUpperCase() == 'ON' || + ifdoc.body && ifdoc.body.isContentEditable); + } catch (e) { + return false; + } + } + + return false; +}; + + +/** + * Sets class names to an element. + * + * @param {Element} elem Element to set class names. + * @param {Array.<string>} classes Class names. + */ +i18n.input.common.dom.setClasses = function(elem, classes) { + if (elem) { + for (var i = 0; i < classes.length; i++) { + if (i == 0) { + goog.dom.classlist.set(elem, classes[0]); + } else { + goog.dom.classlist.add(elem, classes[i]); + } + } + } +}; + + +/** + * Check the iframe whether is the same domain as the current domain. + * Returns the iframe content document when it's the same domain, + * otherwise return null. + * + * @param {!Element} element The iframe element. + * @return {Document} The iframe content document. + */ +i18n.input.common.dom.getSameDomainFrameDoc = function(element) { + var uid = goog.getUid(document); + var frameUid = goog.getUid(element); + var states = i18n.input.common.dom.sameDomainIframes_[uid]; + if (!states) { + states = i18n.input.common.dom.sameDomainIframes_[uid] = {}; + } + /** @preserveTry */ + try { + var url = window.location.href || ''; + //Note: cross-domain IFRAME's src can be: + // http://www... + // https://www.... + // //www. + // Non-cross-domain IFRAME's src can be: + // javascript:... + // javascript://... + // abc:... + // abc://... + // abc//... + // path/index.html + if (!(frameUid in states)) { + if (element.src) { + var pos = element.src.indexOf('//'); + var protocol = pos < 0 ? 'N/A' : element.src.slice(0, pos); + states[frameUid] = (protocol != '' && + protocol != 'http:' && + protocol != 'https:' || + goog.uri.utils.haveSameDomain(element.src, url)); + } else { + states[frameUid] = true; + } + } + return states[frameUid] ? goog.dom.getFrameContentDocument(element) : null; + } catch (e) { + states[frameUid] = false; + return null; + } +}; + + +/** + * Gets the same domain iframe or frame document in given document, default + * given document is current document. + * + * @param {Document=} opt_doc The given document. + * @return {Array.<!Document>} The same domain iframe document. + */ +i18n.input.common.dom.getSameDomainDocuments = function(opt_doc) { + var doc = opt_doc || document; + var iframes = []; + var rets = []; + goog.array.extend(iframes, + doc.getElementsByTagName(goog.dom.TagName.IFRAME), + doc.getElementsByTagName(goog.dom.TagName.FRAME)); + goog.array.forEach(iframes, function(frame) { + var frameDoc = i18n.input.common.dom.getSameDomainFrameDoc(frame); + frameDoc && rets.push(frameDoc); + }); + return rets; +}; + + +/** + * Create the iframe in given document or default document. Then the input tool + * UI element will be create inside the iframe document to avoid CSS conflict. + * + * @param {Document=} opt_doc The given document. + * @return {!Element} The iframe element. + */ +i18n.input.common.dom.createIframeWrapper = function(opt_doc) { + var doc = opt_doc || document; + var dom = goog.dom.getDomHelper(); + var frame = dom.createDom(goog.dom.TagName.IFRAME, { + 'frameborder': '0', + 'scrolling': 'no', + 'style': 'background-color:transparent;border:0;display:none;' + }); + dom.append(/** @type {!Element} */ (doc.body), frame); + var frameDoc = dom.getFrameContentDocument(frame); + + var css = i18n.input.common.GlobalSettings.alternativeImageUrl ? + i18n.input.common.GlobalSettings.css.replace( + /\/\/ssl.gstatic.com\/inputtools\/images/g, + i18n.input.common.GlobalSettings.alternativeImageUrl) : + i18n.input.common.GlobalSettings.css; + goog.style.installStyles( + 'html body{border:0;margin:0;padding:0} html,body{overflow:hidden}' + + css, /** @type {!Element} */(frameDoc.body)); + return frame; +}; + + +/** + * The property need to be copied from original element to its iframe wrapper. + * + * @type {!Array.<string>} + * @private + */ +i18n.input.common.dom.iframeWrapperProperty_ = ['box-shadow', 'z-index', + 'margin', 'position', 'display']; + + +/** + * Copies the necessary properties value from original element to its iframe + * wrapper element. + * + * @param {Element} element . + * @param {Element} iframe The iframe wrapper element. + */ +i18n.input.common.dom.copyNecessaryStyle = function(element, iframe) { + goog.style.setContentBoxSize(iframe, goog.style.getSize(element)); + goog.array.forEach(i18n.input.common.dom.iframeWrapperProperty_, + function(property) { + goog.style.setStyle(iframe, property, + goog.style.getComputedStyle(element, property)); + }); +}; diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/altdataview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/altdataview.js new file mode 100644 index 0000000..3dd9499 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/altdataview.js @@ -0,0 +1,340 @@ +// 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.AltDataView'); + +goog.require('goog.array'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('goog.style'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.Element'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.inputview.util'); + + + +goog.scope(function() { + + + +/** + * The view for alt data. + * + * @param {goog.events.EventTarget=} opt_eventTarget The parent event target. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.Element} + */ +i18n.input.chrome.inputview.elements.content.AltDataView = function( + opt_eventTarget) { + goog.base(this, '', i18n.input.chrome.inputview.elements.ElementType. + ALTDATA_VIEW, opt_eventTarget); + + /** + * The alternative elements. + * + * @type {!Array.<!Element>} + * @private + */ + this.altdataElements_ = []; +}; +goog.inherits(i18n.input.chrome.inputview.elements.content.AltDataView, + i18n.input.chrome.inputview.elements.Element); +var AltDataView = i18n.input.chrome.inputview.elements.content.AltDataView; + + +/** + * The padding between the alt data view and the key. + * + * @type {number} + * @private + */ +AltDataView.PADDING_ = 6; + + +/** + * The distance between finger to altdata view which will cancel the altdata + * view. + * + * @type {number} + * @private + */ +AltDataView.FINGER_DISTANCE_TO_CANCEL_ALTDATA_ = 100; + + +/** + * 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. + * + * @type {!Element} + * @private + */ +AltDataView.prototype.coverElement_; + + +/** + * The index of the alternative element which is highlighted. + * + * @type {number} + * @private + */ +AltDataView.prototype.highlightIndex_ = 0; + + +/** + * The key which trigger this alternative data view. + * + * @type {!i18n.input.chrome.inputview.elements.content.SoftKey} + */ +AltDataView.prototype.triggeredBy; + + +/** @override */ +AltDataView.prototype.createDom = function() { + goog.base(this, 'createDom'); + + var dom = this.getDomHelper(); + var elem = this.getElement(); + goog.dom.classlist.add(elem, i18n.input.chrome.inputview.Css.ALTDATA_VIEW); + this.coverElement_ = dom.createDom(goog.dom.TagName.DIV, + i18n.input.chrome.inputview.Css.ALTDATA_COVER); + dom.appendChild(document.body, this.coverElement_); + goog.style.setElementShown(this.coverElement_, false); + + this.coverElement_['view'] = this; +}; + + +/** @override */ +AltDataView.prototype.enterDocument = function() { + goog.base(this, 'enterDocument'); + + goog.style.setElementShown(this.getElement(), false); +}; + + +/** + * Shows the alt data viwe. + * + * @param {!i18n.input.chrome.inputview.elements.content.SoftKey} key The key + * triggerred this altdata view. + * @param {boolean} isRTL Whether to show the key characters as RTL. + */ +AltDataView.prototype.show = function(key, isRTL) { + this.triggeredBy = key; + var coordinate = goog.style.getClientPosition(key.getElement()); + var x = coordinate.x; + var y = coordinate.y; + var width = key.availableWidth; + var height = key.availableHeight; + var ElementType = i18n.input.chrome.inputview.elements.ElementType; + var characters; + if (key.type == ElementType.CHARACTER_KEY) { + key = /** @type {!i18n.input.chrome.inputview.elements.content. + CharacterKey} */ (key); + characters = key.getAltCharacters(); + } else if (key.type == ElementType.COMPACT_KEY) { + key = /** @type {!i18n.input.chrome.inputview.elements.content. + CompactKey} */ (key); + characters = key.getMoreCharacters(); + if (key.hintText) { + goog.array.insertAt(characters, key.hintText, 0); + } + } + if (!characters || characters.length == 0) { + return; + } + + goog.style.setElementShown(this.getElement(), true); + this.getDomHelper().removeChildren(this.getElement()); + // The total width of the characters + the separators, every separator has + // width = 1. + var altDataWidth = width * characters.length; + var showingLeft = (x + altDataWidth) > screen.width; + if (showingLeft) { + characters.reverse(); + } + for (var i = 0; i < characters.length; i++) { + var keyElem = this.addKey_(characters[i], isRTL); + goog.style.setSize(keyElem, width, height); + this.altdataElements_.push(keyElem); + if (i != characters.length - 1) { + this.addSeparator_(height); + } + } + var left = x; + if (showingLeft) { + // If no enough space at the right, then make it to the left. + left = x + width - altDataWidth; + this.highlightIndex_ = this.altdataElements_.length - 1; + this.setElementBackground_(this.altdataElements_[this.highlightIndex_], + true); + } else { + this.setElementBackground_(this.altdataElements_[0], true); + } + var elemTop = y - height - AltDataView.PADDING_; + if (elemTop < 0) { + elemTop = y + height + AltDataView.PADDING_; + } + goog.style.setPosition(this.getElement(), left, elemTop); + goog.style.setElementShown(this.coverElement_, true); + this.triggeredBy.setHighlighted(true); +}; + + +/** + * Hides the alt data view. + */ +AltDataView.prototype.hide = function() { + this.altdataElements_ = []; + this.highlightIndex_ = 0; + goog.style.setElementShown(this.getElement(), false); + goog.style.setElementShown(this.coverElement_, false); + this.triggeredBy.setHighlighted(false); +}; + + +/** + * Highlights the item according to the current coordinate of the finger. + * + * @param {number} x . + * @param {number} y . + */ +AltDataView.prototype.highlightItem = function(x, y) { + for (var i = 0; i < this.altdataElements_.length; i++) { + var elem = this.altdataElements_[i]; + var coordinate = goog.style.getClientPosition(elem); + var size = goog.style.getSize(elem); + if (coordinate.x < x && (coordinate.x + size.width) > x) { + this.highlightIndex_ = i; + this.clearAllHighlights_(); + this.setElementBackground_(elem, true); + } + var verticalDist = Math.min(Math.abs(y - coordinate.y), + Math.abs(y - coordinate.y - size.height)); + if (verticalDist > AltDataView. + FINGER_DISTANCE_TO_CANCEL_ALTDATA_) { + this.hide(); + return; + } + } +}; + + +/** + * Clears all the highlights. + * + * @private + */ +AltDataView.prototype.clearAllHighlights_ = + function() { + for (var i = 0; i < this.altdataElements_.length; i++) { + this.setElementBackground_(this.altdataElements_[i], false); + } +}; + + +/** + * Sets the background style of the element. + * + * @param {!Element} element The element. + * @param {boolean} highlight True to highlight the element. + * @private + */ +AltDataView.prototype.setElementBackground_ = + function(element, highlight) { + if (highlight) { + goog.dom.classlist.add(element, i18n.input.chrome.inputview.Css. + ELEMENT_HIGHLIGHT); + } else { + goog.dom.classlist.remove(element, i18n.input.chrome.inputview.Css. + ELEMENT_HIGHLIGHT); + } +}; + + +/** + * Gets the highlighted character. + * + * @return {string} The character. + */ +AltDataView.prototype.getHighlightedCharacter = function() { + return goog.dom.getTextContent(this.altdataElements_[this.highlightIndex_]); +}; + + +/** + * Adds a alt data key into the view. + * + * @param {string} character The alt character. + * @param {boolean} isRTL Whether to show the character as RTL. + * @return {!Element} The create key element. + * @private + */ +AltDataView.prototype.addKey_ = function(character, isRTL) { + var dom = this.getDomHelper(); + var keyElem = dom.createDom(goog.dom.TagName.DIV, + i18n.input.chrome.inputview.Css.ALTDATA_KEY, + i18n.input.chrome.inputview.util.getVisibleCharacter(character)); + keyElem.style.direction = isRTL ? 'rtl' : 'ltr'; + dom.appendChild(this.getElement(), keyElem); + return keyElem; +}; + + +/** + * Adds a separator. + * + * @param {number} height . + * @private + */ +AltDataView.prototype.addSeparator_ = function(height) { + var dom = this.getDomHelper(); + var tableCell = dom.createDom(goog.dom.TagName.DIV, + i18n.input.chrome.inputview.Css.TABLE_CELL); + tableCell.style.height = height + 'px'; + var separator = dom.createDom(goog.dom.TagName.DIV, + i18n.input.chrome.inputview.Css.ALTDATA_SEPARATOR); + dom.appendChild(tableCell, separator); + dom.appendChild(this.getElement(), tableCell); +}; + + +/** + * Gets the cover element. + * + * @return {!Element} The cover element. + */ +AltDataView.prototype.getCoverElement = function() { + return this.coverElement_; +}; + + +/** @override */ +AltDataView.prototype.resize = function(width, height) { + goog.base(this, 'resize', width, height); + + goog.style.setSize(this.coverElement_, width, height); +}; + + +/** @override */ +AltDataView.prototype.disposeInternal = function() { + this.getElement()['view'] = null; + + goog.base(this, 'disposeInternal'); +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidate.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidate.js new file mode 100644 index 0000000..4540938 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidate.js @@ -0,0 +1,113 @@ +// 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.Candidate'); + +goog.require('goog.dom.classlist'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.Element'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.message.Name'); + +goog.scope(function() { +var ElementType = i18n.input.chrome.inputview.elements.ElementType; +var Css = i18n.input.chrome.inputview.Css; +var TagName = goog.dom.TagName; +var Name = i18n.input.chrome.message.Name; + + + +/** + * The candidate component. + * + * @param {string} id . + * @param {!Object} candidate . + * @param {i18n.input.chrome.inputview.elements.content.Candidate.Type} + * candidateType . + * @param {number} height . + * @param {boolean} isDefault . + * @param {number=} opt_width . + * @param {goog.events.EventTarget=} opt_eventTarget . + * @constructor + * @extends {i18n.input.chrome.inputview.elements.Element} + */ +i18n.input.chrome.inputview.elements.content.Candidate = function(id, + candidate, candidateType, height, isDefault, opt_width, opt_eventTarget) { + goog.base(this, id, ElementType.CANDIDATE, opt_eventTarget); + + /** @type {!Object} */ + this.candidate = candidate; + + /** @type {i18n.input.chrome.inputview.elements.content.Candidate.Type} */ + this.candidateType = candidateType; + + /** @type {number} */ + this.width = opt_width || 0; + + /** @type {number} */ + this.height = height; + + /** @type {boolean} */ + this.isDefault = isDefault; +}; +var Candidate = i18n.input.chrome.inputview.elements.content.Candidate; +goog.inherits(Candidate, i18n.input.chrome.inputview.elements.Element); + + +/** + * The type of this candidate. + * + * @enum {number} + */ +Candidate.Type = { + CANDIDATE: 0, + NUMBER: 1 +}; + + +/** @override */ +Candidate.prototype.createDom = function() { + goog.base(this, 'createDom'); + + var dom = this.getDomHelper(); + var elem = this.getElement(); + goog.dom.classlist.add(elem, Css.CANDIDATE); + if (this.candidate['isEmoji']) { + goog.dom.classlist.add(elem, Css.EMOJI_FONT); + } + dom.setTextContent(elem, this.candidate[Name.CANDIDATE]); + elem.style.height = this.height + 'px'; + if (this.width > 0) { + elem.style.width = this.width + 'px'; + } + if (this.isDefault) { + goog.dom.classlist.add(elem, Css.CANDIDATE_DEFAULT); + } + if (!!this.candidate[Name.IS_AUTOCORRECT]) { + goog.dom.classlist.add(elem, Css.CANDIDATE_AUTOCORRECT); + } +}; + + +/** @override */ +Candidate.prototype.setHighlighted = function(highlight) { + if (highlight) { + goog.dom.classlist.add(this.getElement(), Css.CANDIDATE_HIGHLIGHT); + } else { + goog.dom.classlist.remove(this.getElement(), Css.CANDIDATE_HIGHLIGHT); + } +}; + + +}); // goog.scope + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidatebutton.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidatebutton.js new file mode 100644 index 0000000..b7aa484 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidatebutton.js @@ -0,0 +1,117 @@ +// 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.CandidateButton'); + +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('goog.style'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.Element'); + + + +goog.scope(function() { +var ElementType = i18n.input.chrome.inputview.elements.ElementType; +var Css = i18n.input.chrome.inputview.Css; + + + +/** + * The icon button in the candidate view. + * + * @param {string} id . + * @param {ElementType} type . + * @param {string} iconCss . + * @param {string} text . + * @param {!goog.events.EventTarget=} opt_eventTarget . + * @constructor + * @extends {i18n.input.chrome.inputview.elements.Element} + */ +i18n.input.chrome.inputview.elements.content.CandidateButton = function( + id, type, iconCss, text, opt_eventTarget) { + goog.base(this, id, type, opt_eventTarget); + + /** @type {string} */ + this.text = text; + + /** @type {string} */ + this.iconCss = iconCss; +}; +var CandidateButton = i18n.input.chrome.inputview.elements.content. + CandidateButton; +goog.inherits(CandidateButton, i18n.input.chrome.inputview.elements.Element); + + +/** @type {!Element} */ +CandidateButton.prototype.iconCell; + + +/** @type {!Element} */ +CandidateButton.prototype.separatorCell; + + +/** @override */ +CandidateButton.prototype.createDom = function() { + goog.base(this, 'createDom'); + + var dom = this.getDomHelper(); + var elem = this.getElement(); + goog.dom.classlist.addAll(elem, [Css.CANDIDATE_INTER_CONTAINER, + Css.CANDIDATE_BUTTON]); + + this.separatorCell = this.createSeparator_(); + + this.iconCell = dom.createDom(goog.dom.TagName.DIV, Css.TABLE_CELL); + dom.appendChild(elem, this.iconCell); + + var iconElem = dom.createDom(goog.dom.TagName.DIV, Css.INLINE_DIV); + if (this.iconCss) { + goog.dom.classlist.add(iconElem, this.iconCss); + } + if (this.text) { + dom.setTextContent(iconElem, this.text); + } + dom.appendChild(this.iconCell, iconElem); +}; + + +/** + * Creates a separator. + * + * @private + */ +CandidateButton.prototype.createSeparator_ = function() { + var dom = this.getDomHelper(); + var tableCell = dom.createDom(goog.dom.TagName.DIV, + i18n.input.chrome.inputview.Css.TABLE_CELL); + var separator = dom.createDom(goog.dom.TagName.DIV, + i18n.input.chrome.inputview.Css.CANDIDATE_SEPARATOR); + separator.style.height = '32%'; + dom.appendChild(tableCell, separator); + dom.appendChild(this.getElement(), tableCell); + return tableCell; +}; + + +/** @override */ +CandidateButton.prototype.resize = function(width, height) { + goog.style.setSize(this.separatorCell, 1, height); + goog.style.setSize(this.iconCell, width - 1, height); + + goog.base(this, 'resize', width, height); +}; + + +}); // goog.scope + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidateview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidateview.js new file mode 100644 index 0000000..98e3d10 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidateview.js @@ -0,0 +1,367 @@ +// 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.CandidateView'); + +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('goog.object'); +goog.require('goog.style'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.Element'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.inputview.elements.content.Candidate'); +goog.require('i18n.input.chrome.inputview.elements.content.CandidateButton'); +goog.require('i18n.input.chrome.message.Name'); + + + +goog.scope(function() { +var Css = i18n.input.chrome.inputview.Css; +var TagName = goog.dom.TagName; +var Candidate = i18n.input.chrome.inputview.elements.content.Candidate; +var Type = i18n.input.chrome.inputview.elements.content.Candidate.Type; +var ElementType = i18n.input.chrome.inputview.elements.ElementType; +var Element = i18n.input.chrome.inputview.elements.Element; +var content = i18n.input.chrome.inputview.elements.content; +var Name = i18n.input.chrome.message.Name; + + + +/** + * The candidate view. + * + * @param {string} id The id. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.Element} + */ +i18n.input.chrome.inputview.elements.content.CandidateView = function(id, + opt_eventTarget) { + goog.base(this, id, ElementType.CANDIDATE_VIEW, opt_eventTarget); + + /** + * The icons. + * + * @private {!Array.<!i18n.input.chrome.inputview.elements.Element>} + */ + this.iconButtons_ = []; + + this.iconButtons_[CandidateView.IconType.BACK] = new content.CandidateButton( + '', ElementType.BACK_BUTTON, '', + chrome.i18n.getMessage('HANDWRITING_BACK'), this); + this.iconButtons_[CandidateView.IconType.SHRINK_CANDIDATES] = new content. + CandidateButton('', ElementType.SHRINK_CANDIDATES, + Css.SHRINK_CANDIDATES_ICON, '', this); + this.iconButtons_[CandidateView.IconType.EXPAND_CANDIDATES] = new content. + CandidateButton('', ElementType.EXPAND_CANDIDATES, + Css.EXPAND_CANDIDATES_ICON, '', this); +}; +goog.inherits(i18n.input.chrome.inputview.elements.content.CandidateView, + i18n.input.chrome.inputview.elements.Element); +var CandidateView = i18n.input.chrome.inputview.elements.content.CandidateView; + + +/** + * The icon type at the right of the candiate view. + * + * @enum {number} + */ +CandidateView.IconType = { + BACK: 0, + SHRINK_CANDIDATES: 1, + EXPAND_CANDIDATES: 2 +}; + + +/** + * How many candidates in this view. + * + * @type {number} + */ +CandidateView.prototype.candidateCount = 0; + + +/** + * The padding between candidates. + * + * @type {number} + * @private + */ +CandidateView.PADDING_ = 50; + + +/** + * The width in weight which stands for the entire row. It is used for the + * alignment of the number row. + * + * @private {number} + */ +CandidateView.prototype.widthInWeight_ = 0; + + +/** + * True if it is showing candidate. + * + * @type {boolean} + */ +CandidateView.prototype.showingCandidates = false; + + +/** + * true if it is showing number row. + * + * @type {boolean} + */ +CandidateView.prototype.showingNumberRow = false; + + +/** + * The width for a candidate when showing in THREE_CANDIDATE mode. + * + * @type {number} + * @private + */ +CandidateView.WIDTH_FOR_THREE_CANDIDATES_ = 235; + + +/** + * The width of the icon at the right of the candidate view, it would be back + * icon, hide candidates icon, or show candidates icon. + * + * @private {number} + */ +CandidateView.ICON_WIDTH_ = 120; + + +/** + * The handwriting keyset code. + * + * @private {string} + */ +CandidateView.HANDWRITING_VIEW_CODE_ = 'hwt'; + + +/** + * The width of the inter container. + * + * @private {number} + */ +CandidateView.prototype.interContainerWidth_ = 0; + + +/** @override */ +CandidateView.prototype.createDom = function() { + goog.base(this, 'createDom'); + + var dom = this.getDomHelper(); + var elem = this.getElement(); + this.interContainer_ = dom.createDom(TagName.DIV, + Css.CANDIDATE_INTER_CONTAINER); + dom.appendChild(elem, this.interContainer_); + + for (var i = 0; i < this.iconButtons_.length; i++) { + var button = this.iconButtons_[i]; + button.render(elem); + button.setVisible(false); + } + + // CandidateView is a container which doesn't handle any event and could be + // taken as a empty area, so don't attach the view. + elem['view'] = null; +}; + + +/** + * Hides the number row. + */ +CandidateView.prototype.hideNumberRow = function() { + if (this.showingNumberRow) { + this.getDomHelper().removeChildren(this.interContainer_); + this.showingNumberRow = false; + } +}; + + +/** + * Shows the number row. + */ +CandidateView.prototype.showNumberRow = function() { + goog.dom.classlist.remove(this.getElement(), + i18n.input.chrome.inputview.Css.THREE_CANDIDATES); + var dom = this.getDomHelper(); + var numberWidth = this.width / this.widthInWeight_ - 1; + dom.removeChildren(this.interContainer_); + for (var i = 0; i < 10; i++) { + var candidateElem = new Candidate(String(i), goog.object.create( + Name.CANDIDATE, String((i + 1) % 10)), + Type.NUMBER, this.height, false, numberWidth, this); + candidateElem.render(this.interContainer_); + } + this.showingNumberRow = true; +}; + + +/** + * Shows the candidates. + * + * @param {!Array.<!Object>} candidates The candidate list. + * @param {boolean} showThreeCandidates . + * @param {boolean=} opt_expandable True if the candidates would be shown + * in expanded view. + */ +CandidateView.prototype.showCandidates = function(candidates, + showThreeCandidates, opt_expandable) { + this.clearCandidates(); + if (candidates.length > 0) { + if (showThreeCandidates) { + this.addThreeCandidates_(candidates); + } else { + this.addFullCandidates_(candidates); + if (opt_expandable) { + this.switchToIcon(CandidateView.IconType.EXPAND_CANDIDATES, + this.candidateCount < candidates.length); + } + } + this.showingCandidates = true; + } +}; + + +/** + * Adds the candidates in THREE-CANDIDATE mode. + * + * @param {!Array.<!Object>} candidates The candidate list. + * @private + */ +CandidateView.prototype.addThreeCandidates_ = function(candidates) { + goog.dom.classlist.add(this.getElement(), + i18n.input.chrome.inputview.Css.THREE_CANDIDATES); + var num = Math.min(3, candidates.length); + var dom = this.getDomHelper(); + for (var i = 0; i < num; i++) { + var candidateElem = new Candidate(String(i), candidates[i], Type.CANDIDATE, + this.height, i == 1 || num == 1, CandidateView. + WIDTH_FOR_THREE_CANDIDATES_, this); + candidateElem.render(this.interContainer_); + } + this.candidateCount = num; +}; + + +/** + * Clears the candidates. + */ +CandidateView.prototype.clearCandidates = function() { + if (this.showingCandidates) { + this.candidateCount = 0; + this.getDomHelper().removeChildren(this.interContainer_); + this.showingCandidates = false; + } +}; + + +/** + * Adds candidates into the view, as many as the candidate bar can support. + * + * @param {!Array.<!Object>} candidates The candidate list. + * @private + */ +CandidateView.prototype.addFullCandidates_ = function(candidates) { + goog.dom.classlist.remove(this.getElement(), + i18n.input.chrome.inputview.Css.THREE_CANDIDATES); + var totalWidth = Math.floor(this.width - CandidateView.ICON_WIDTH_); + var w = 0; + var dom = this.getDomHelper(); + var i; + for (i = 0; i < candidates.length; i++) { + var candidateElem = new Candidate(String(i), candidates[i], Type.CANDIDATE, + this.height, false, undefined, this); + candidateElem.render(this.interContainer_); + var size = goog.style.getSize(candidateElem.getElement()); + var candidateWidth = size.width + CandidateView.PADDING_ * 2; + // 1px is the width of the separator. + w += candidateWidth + 1; + + if (w >= totalWidth) { + this.interContainer_.removeChild(candidateElem.getElement()); + break; + } + goog.style.setWidth(candidateElem.getElement(), candidateWidth); + } + this.candidateCount = i; +}; + + +/** + * Sets the widthInWeight which equals to a total line in the + * keyset view and it is used for alignment of number row. + * + * @param {number} widthInWeight . + */ +CandidateView.prototype.setWidthInWeight = function(widthInWeight) { + this.widthInWeight_ = widthInWeight; +}; + + +/** @override */ +CandidateView.prototype.resize = function(width, height) { + goog.style.setSize(this.getElement(), width, height); + this.interContainer_.style.height = height + 'px'; + for (var i = 0; i < this.iconButtons_.length; i++) { + var button = this.iconButtons_[i]; + button.resize(CandidateView.ICON_WIDTH_, height); + } + + goog.base(this, 'resize', width, height); + + if (this.showingNumberRow) { + this.showNumberRow(); + } +}; + + +/** + * Switches to the icon, or hide it. + * + * @param {number} type . + * @param {boolean} visible The visibility of back button. + */ +CandidateView.prototype.switchToIcon = function(type, visible) { + for (var i = 0; i < this.iconButtons_.length; i++) { + this.iconButtons_[i].setVisible(false); + } + + this.iconButtons_[type].setVisible(visible); +}; + + +/** + * Updates the candidate view by key set changing. + * + * @param {string} keyset . + * @param {boolean} isPasswordBox . + * @param {boolean} isRTL . + */ +CandidateView.prototype.updateByKeyset = function( + keyset, isPasswordBox, isRTL) { + this.switchToIcon(CandidateView.IconType.BACK, + keyset == CandidateView.HANDWRITING_VIEW_CODE_); + if (isPasswordBox && keyset.indexOf('compact') != -1) { + this.showNumberRow(); + } else { + this.hideNumberRow(); + } + this.interContainer_.style.direction = isRTL ? 'rtl' : 'ltr'; +}; +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/canvasview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/canvasview.js new file mode 100644 index 0000000..e4b8a84 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/canvasview.js @@ -0,0 +1,312 @@ +// 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.CanvasView'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('goog.style'); +goog.require('i18n.input.chrome.DataSource'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.Element'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.inputview.elements.Weightable'); +goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey'); +goog.require('i18n.input.chrome.message.Type'); +goog.require('i18n.input.hwt.Canvas'); +goog.require('i18n.input.hwt.StrokeHandler'); + + + +goog.scope(function() { +var Canvas = i18n.input.hwt.Canvas; +var Css = i18n.input.chrome.inputview.Css; +var ElementType = i18n.input.chrome.inputview.elements.ElementType; +var FunctionalKey = i18n.input.chrome.inputview.elements.content.FunctionalKey; +var Name = i18n.input.chrome.message.Name; +var Type = i18n.input.chrome.message.Type; + + + +/** + * The handwriting canvas view. + * + * @param {string} id The id. + * @param {number} widthInWeight The width in weight unit. + * @param {number} heightInWeight The height in weight unit. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @param {i18n.input.chrome.inputview.Adapter=} opt_adapter . + * @constructor + * @extends {i18n.input.chrome.inputview.elements.Element} + * @implements {i18n.input.chrome.inputview.elements.Weightable} + */ +i18n.input.chrome.inputview.elements.content.CanvasView = function(id, + widthInWeight, heightInWeight, opt_eventTarget, opt_adapter) { + goog.base(this, id, ElementType.CANVAS_VIEW, opt_eventTarget); + + /** + * @private {!Canvas} + */ + this.canvas_ = new Canvas(document, this.getDomHelper(), opt_eventTarget, + CanvasView.INK_WIDTH_, CanvasView.INK_COLOR_); + + /** + * The weight of the width. + * + * @type {number} + * @private + */ + this.widthInWeight_ = widthInWeight; + + /** + * The weight of the height. + * + * @type {number} + * @private + */ + this.heightInWeight_ = heightInWeight; + + /** + * The bus channel to communicate with background. + * + * @private {i18n.input.chrome.inputview.Adapter} + */ + this.adapter_ = goog.asserts.assertObject(opt_adapter); + + this.pointerConfig.stopEventPropagation = false; +}; +goog.inherits(i18n.input.chrome.inputview.elements.content.CanvasView, + i18n.input.chrome.inputview.elements.Element); +var CanvasView = i18n.input.chrome.inputview.elements.content.CanvasView; + + +/** + * Width of the ink line. + * + * @type {number} + * @private + */ +CanvasView.INK_WIDTH_ = 6; + + +/** + * Color of the ink before it has been recognized. + * + * @type {string} + * @private + */ +CanvasView.INK_COLOR_ = '#111111'; + + +/** + * The div to show network error message. + * + * @type {!Element} + * @private + */ +CanvasView.prototype.networkErrorDiv_; + + +/** + * The div to show privacy information message. + * + * @type {!Element} + * @private + */ +CanvasView.prototype.privacyDiv_; + + +/** + * The confirm button of privacy information. + * + * @private {!FunctionalKey} + */ +CanvasView.prototype.confirmBtn_; + + +/** + * The cover mask element. + * + * @private {!Element} + */ +CanvasView.prototype.coverElement_; + + +/** @override */ +CanvasView.prototype.createDom = function() { + goog.base(this, 'createDom'); + + var container = this.getElement(); + var dom = this.getDomHelper(); + goog.dom.classlist.add(container, Css.CANVAS_VIEW); + this.coverElement_ = dom.createDom(goog.dom.TagName.DIV, + Css. HANDWRITING_PRIVACY_COVER); + dom.appendChild(container, this.coverElement_); + goog.style.setElementShown(this.coverElement_, false); + + + this.canvas_.render(container); + goog.dom.classlist.add(this.canvas_.getElement(), Css.CANVAS); + + this.networkErrorDiv_ = dom.createDom( + goog.dom.TagName.DIV, Css.HANDWRITING_NETWORK_ERROR); + dom.setTextContent(this.networkErrorDiv_, + chrome.i18n.getMessage('HANDWRITING_NETOWRK_ERROR')); + goog.style.setElementShown(this.networkErrorDiv_, false); + dom.appendChild(container, this.networkErrorDiv_); + + this.privacyDiv_ = dom.createDom(goog.dom.TagName.DIV, + [Css.HANDWRITING_PRIVACY_INFO, Css.HANDWRITING_PRIVACY_INFO_HIDDEN]); + var textDiv = dom.createDom(goog.dom.TagName.DIV); + dom.setTextContent(textDiv, + chrome.i18n.getMessage('HANDWRITING_PRIVACY_INFO')); + dom.appendChild(this.privacyDiv_, textDiv); + this.confirmBtn_ = new FunctionalKey( + '', ElementType.HWT_PRIVACY_GOT_IT, chrome.i18n.getMessage('GOT_IT'), ''); + this.confirmBtn_.render(this.privacyDiv_); + + dom.appendChild(container, this.privacyDiv_); +}; + + +/** @override */ +CanvasView.prototype.enterDocument = function() { + goog.base(this, 'enterDocument'); + this.getHandler(). + listen(this.canvas_.getStrokeHandler(), + i18n.input.hwt.StrokeHandler.EventType.STROKE_END, + this.onStrokeEnd_). + listen(this.adapter_, + [i18n.input.chrome.DataSource.EventType.CANDIDATES_BACK, + Type.HWT_NETWORK_ERROR], + this.onNetworkState_). + listen(this.adapter_, Type.HWT_PRIVACY_INFO, this.onShowPrivacyInfo_). + listen(this.adapter_, Type.HWT_PRIVACY_GOT_IT, + this.onConfirmPrivacyInfo_); +}; + + +/** @override */ +CanvasView.prototype.setHighlighted = goog.nullFunction; + + +/** @override */ +CanvasView.prototype.getWidthInWeight = function() { + return this.widthInWeight_; +}; + + +/** @override */ +CanvasView.prototype.getHeightInWeight = function() { + return this.heightInWeight_; +}; + + +/** @override */ +CanvasView.prototype.resize = function(width, height) { + goog.base(this, 'resize', width, height); + + var elem = this.getElement(); + elem.style.width = this.coverElement_.style.width = width + 'px'; + elem.style.height = this.coverElement_.style.height = height + 'px'; + + this.networkErrorDiv_.style.top = this.privacyDiv_.style.top = + Math.round(height / 2 - 50) + 'px'; + this.networkErrorDiv_.style.left = this.privacyDiv_.style.left = + Math.round(width / 2 - 150) + 'px'; + this.confirmBtn_.resize(100, 60); + + this.canvas_.setSize(height, width); +}; + + +/** + * Prepare the input data for a recognition request, including the + * ink, context, and language. + * + * @private + */ +CanvasView.prototype.onStrokeEnd_ = function() { + // Reformat the ink into the format expected by the input servers. + var strokes = goog.array.map(this.canvas_.strokeList, + function(stroke) { + return [goog.array.map(stroke, function(point) { return point.x; }), + goog.array.map(stroke, function(point) { return point.y; }), + goog.array.map(stroke, function(point) { return point.time; })]; + }); + var elem = this.getElement(); + var payload = { + 'strokes': strokes, + 'width': elem.style.width, + 'height': elem.style.height + }; + + this.adapter_.sendHwtRequest(payload); +}; + + +/** + * Clears the strokes on canvas. + */ +CanvasView.prototype.reset = function() { + this.canvas_.reset(); +}; + + +/** + * Whether there are strokes on canvas. + * + * @return {boolean} Whether there are strokes on canvas. + */ +CanvasView.prototype.hasStrokesOnCanvas = function() { + return this.canvas_.strokeList.length > 0; +}; + + +/** + * Show or hide network error message div. + * + * @param {!goog.events.Event} e + * @private + */ +CanvasView.prototype.onNetworkState_ = function(e) { + goog.style.setElementShown( + this.networkErrorDiv_, e.type == Type.HWT_NETWORK_ERROR); +}; + + +/** + * Shows the privacy information. Show on first time seeing the handwriting UI + * for 6 seconds, then fade out over 2 seconds. + * + * @private + */ +CanvasView.prototype.onShowPrivacyInfo_ = function() { + goog.style.setElementShown(this.coverElement_, true); + goog.dom.classlist.remove(this.privacyDiv_, + Css.HANDWRITING_PRIVACY_INFO_HIDDEN); +}; + + +/** + * Handler on user confirming the privacy information. + * + * @private + */ +CanvasView.prototype.onConfirmPrivacyInfo_ = function() { + goog.style.setElementShown(this.coverElement_, false); + goog.dom.classlist.add(this.privacyDiv_, Css.HANDWRITING_PRIVACY_INFO_HIDDEN); +}; +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/character.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/character.js new file mode 100644 index 0000000..5e27143 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/character.js @@ -0,0 +1,196 @@ +// 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.Character'); + +goog.require('goog.dom'); +goog.require('goog.dom.classlist'); +goog.require('goog.style'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.Element'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.inputview.util'); + + + +goog.scope(function() { + + + +/** + * The letter in the soft key. + * + * @param {string} id The id. + * @param {!i18n.input.chrome.inputview.elements.content.CharacterModel} model + * The character model. + * @param {boolean} isRTL Whether the character is shown in a RTL layout. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.Element} + */ +i18n.input.chrome.inputview.elements.content.Character = function( + id, model, isRTL) { + goog.base(this, id, i18n.input.chrome.inputview.elements.ElementType. + CHARACTER); + + /** + * The model. + * + * @type {!i18n.input.chrome.inputview.elements.content.CharacterModel} + * @private + */ + this.characterModel_ = model; + + /** + * Whether the character is shown in a RTL layout. + * + * @type {boolean} + * @private + */ + this.isRTL_ = isRTL; +}; +goog.inherits(i18n.input.chrome.inputview.elements.content.Character, + i18n.input.chrome.inputview.elements.Element); +var Character = i18n.input.chrome.inputview.elements.content.Character; + + +/** + * The padding. + * + * @type {number} + * @private + */ +Character.PADDING_ = 2; + + +/** @override */ +Character.prototype.createDom = function() { + goog.base(this, 'createDom'); + + var elem = this.getElement(); + var dom = this.getDomHelper(); + this.getElement()['view'] = null; + var character = this.characterModel_.getContent(); + dom.appendChild(elem, dom.createTextNode(character)); + goog.dom.classlist.add(elem, i18n.input.chrome.inputview.Css.CHARACTER); + if (/[0-9]/.test(character)) { + // Digits 0-9 looks bigger than it should be, so decrease the font-size for + // them. + goog.dom.classlist.add(elem, + i18n.input.chrome.inputview.Css.DIGIT_CHARACTER); + } + elem.style.direction = this.isRTL_ ? 'rtl' : 'ltr'; +}; + + +/** + * Reposition the character. + * + * @private + */ +Character.prototype.reposition_ = function() { + var width = this.width; + var height = this.height; + var size = goog.style.getSize(this.getElement()); + var paddingVertical; + var paddingHorizontal; + if (this.characterModel_.isHorizontalAlignCenter()) { + paddingHorizontal = Math.floor((width - size.width) / 2); + } else { + paddingHorizontal = Character.PADDING_; + } + if (this.characterModel_.isVerticalAlignCenter()) { + paddingVertical = Math.floor((height - size.height) / 2); + } else { + paddingVertical = Character.PADDING_; + } + var attributes = this.characterModel_.getPositionAttribute(); + var elem = this.getElement(); + elem.style[attributes[0]] = paddingVertical + 'px'; + elem.style[attributes[1]] = paddingHorizontal + 'px'; +}; + + +/** + * Highlights the letter or not. + */ +Character.prototype.highlight = function() { + if (this.characterModel_.isHighlighted()) { + goog.dom.classlist.add(this.getElement(), + i18n.input.chrome.inputview.Css.CHARACTER_HIGHLIGHT); + } else { + goog.dom.classlist.remove(this.getElement(), + i18n.input.chrome.inputview.Css.CHARACTER_HIGHLIGHT); + } +}; + + +/** + * Updates the content. + */ +Character.prototype.updateContent = function() { + var ch = this.characterModel_.getContent(); + goog.dom.setTextContent(this.getElement(), + i18n.input.chrome.inputview.util.getVisibleCharacter(ch)); +}; + + +/** @override */ +Character.prototype.setVisible = function(visibility) { + this.getElement().style.display = visibility ? 'inline-block' : 'none'; +}; + + +/** @override */ +Character.prototype.resize = function(width, height) { + goog.base(this, 'resize', width, height); + + this.update(); +}; + + +/** @override */ +Character.prototype.update = function() { + this.highlight(); + this.reposition_(); + this.updateContent(); + this.setVisible(this.characterModel_.isVisible()); +}; + + +/** + * Gets the letter. + * + * @return {string} The letter. + */ +Character.prototype.getContent = function() { + return this.characterModel_.getContent(); +}; + + +/** @override */ +Character.prototype.isVisible = function() { + return this.characterModel_.isVisible(); +}; + + +/** + * True if this character is highlighted. + * + * @return {boolean} True if this character is highlighted. + */ +Character.prototype.isHighlighted = function() { + return this.characterModel_.isHighlighted(); +}; + + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/characterkey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/characterkey.js new file mode 100644 index 0000000..5707e15 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/characterkey.js @@ -0,0 +1,299 @@ +// 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.CharacterKey'); + +goog.require('goog.array'); +goog.require('goog.math.Coordinate'); +goog.require('i18n.input.chrome.inputview.StateType'); +goog.require('i18n.input.chrome.inputview.SwipeDirection'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.inputview.elements.content.Character'); +goog.require('i18n.input.chrome.inputview.elements.content.CharacterModel'); +goog.require('i18n.input.chrome.inputview.elements.content.GaussianEstimator'); +goog.require('i18n.input.chrome.inputview.elements.content.SoftKey'); + + + +goog.scope(function() { +var CharacterModel = i18n.input.chrome.inputview.elements.content. + CharacterModel; +var Character = i18n.input.chrome.inputview.elements.content.Character; + +/** + * The class for a character key, it would be symbol or letter key which is + * different than modifier key and functional key. + * + * @param {string} id The id. + * @param {number} keyCode The key code. + * @param {!Array.<string>} characters The characters. + * @param {boolean} isLetterKey True if this is a letter key. + * @param {boolean} hasAltGrCharacterInTheKeyset True if there is altgr + * character in the keyset. + * @param {boolean} alwaysRenderAltGrCharacter True if always renders the altgr + * character. + * @param {!i18n.input.chrome.inputview.StateManager} stateManager The state + * manager. + * @param {boolean} isRTL Whether the key shows characters in a RTL layout. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.content.SoftKey} + */ +i18n.input.chrome.inputview.elements.content.CharacterKey = function(id, + keyCode, characters, isLetterKey, hasAltGrCharacterInTheKeyset, + alwaysRenderAltGrCharacter, stateManager, isRTL, opt_eventTarget) { + goog.base(this, id, i18n.input.chrome.inputview.elements.ElementType. + CHARACTER_KEY, opt_eventTarget); + + /** + * The key code for sending key events. + * + * @type {number} + */ + this.keyCode = keyCode; + + /** + * The content to shown in the soft key. + * + * @type {!Array.<string>} + */ + this.characters = characters; + + /** + * True if this is a letter key. + * + * @type {boolean} + */ + this.isLetterKey = isLetterKey; + + /** + * True if there is altgr character in the keyset. + * + * @type {boolean} + * @private + */ + this.hasAltGrCharacterInTheKeyset_ = hasAltGrCharacterInTheKeyset; + + /** + * The state manager. + * + * @type {!i18n.input.chrome.inputview.StateManager} + * @private + */ + this.stateManager_ = stateManager; + + /** + * Whether the key shows characters in a RTL layout. + * + * @type {boolean} + * @private + */ + this.isRTL_ = isRTL; + + /** + * Whether to always renders the altgr character.. + * + * @type {boolean} + * @private + */ + this.alwaysRenderAltGrCharacter_ = alwaysRenderAltGrCharacter; + + this.pointerConfig.longPressWithPointerUp = true; + this.pointerConfig.longPressDelay = 500; +}; +goog.inherits(i18n.input.chrome.inputview.elements.content.CharacterKey, + i18n.input.chrome.inputview.elements.content.SoftKey); +var CharacterKey = i18n.input.chrome.inputview.elements.content.CharacterKey; + + +/** + * The flickerred character. + * + * @type {string} + */ +CharacterKey.prototype.flickerredCharacter = ''; + + +/** + * The state map. + * + * @type {!Array.<number>} + * @private + */ +CharacterKey.STATE_LIST_ = [ + i18n.input.chrome.inputview.StateType.DEFAULT, + i18n.input.chrome.inputview.StateType.SHIFT, + i18n.input.chrome.inputview.StateType.ALTGR, + i18n.input.chrome.inputview.StateType.ALTGR | + i18n.input.chrome.inputview.StateType.SHIFT +]; + + +/** @override */ +CharacterKey.prototype.createDom = function() { + goog.base(this, 'createDom'); + + var elem = this.getElement(); + var dom = this.getDomHelper(); + + for (var i = 0; i < CharacterKey.STATE_LIST_.length; i++) { + var ch = this.characters.length > i ? this.characters[i] : ''; + if (ch && ch != '\x00') { + var model = new CharacterModel(ch, this.isLetterKey, + this.hasAltGrCharacterInTheKeyset_, + this.alwaysRenderAltGrCharacter_, + CharacterKey.STATE_LIST_[i], + this.stateManager_, + this.getCapslockCharacter_(i)); + var character = new Character(this.id + '-' + i, model, this.isRTL_); + this.addChild(character, true); + } + } +}; + + +/** + * Gets the capslock character if have. + * + * @param {number} i . + * @private + * @return {string} . + */ +CharacterKey.prototype.getCapslockCharacter_ = function(i) { + var capslockCharacterIndex = i + 4; + if (this.characters.length > capslockCharacterIndex) { + return this.characters[capslockCharacterIndex]; + } + + return ''; +}; + + +/** @override */ +CharacterKey.prototype.resize = function(width, + height) { + goog.base(this, 'resize', width, height); + + for (var i = 0; i < this.getChildCount(); i++) { + var child = /** @type {!i18n.input.chrome.inputview.elements.Element} */ ( + this.getChildAt(i)); + child.resize(this.availableWidth, this.availableHeight); + } + + var elem = this.getElement(); + this.topLeftCoordinate = goog.style.getClientPosition(elem); + this.centerCoordinate = new goog.math.Coordinate( + this.topLeftCoordinate.x + this.availableWidth / 2, + this.topLeftCoordinate.y + this.availableHeight / 2); + this.estimator = new i18n.input.chrome.inputview.elements.content. + GaussianEstimator(this.centerCoordinate, + this.stateManager_.covariance.getValue(this.type), + this.availableHeight / this.availableWidth); +}; + + +/** + * Gets the alternative characters. + * + * @return {!Array.<string>} The characters. + */ +CharacterKey.prototype.getAltCharacters = + function() { + var altCharacters = []; + for (var i = 0; i < this.characters.length; i++) { + var ch = this.characters[i]; + if (ch && ch != '\x00' && ch != this.getActiveCharacter()) { + goog.array.insert(altCharacters, ch); + } + } + return altCharacters; +}; + + +/** + * The active letter. + * + * @return {string} The active letter. + */ +CharacterKey.prototype.getActiveCharacter = + function() { + if (this.flickerredCharacter) { + return this.flickerredCharacter; + } + + for (var i = 0; i < this.getChildCount(); i++) { + var child = /** @type {!i18n.input.chrome.inputview.elements.content. + Character} */ (this.getChildAt(i)); + if (child.isHighlighted()) { + return child.getContent(); + } + } + return this.getChildAt(0).getContent(); +}; + + + +/** + * Gets the character by gesture direction. + * + * @param {boolean} upOrDown True if up, false if down. + * @return {string} The character content. + */ +CharacterKey.prototype.getCharacterByGesture = + function(upOrDown) { + var hasAltGrState = this.stateManager_.hasState( + i18n.input.chrome.inputview.StateType.ALTGR); + var hasShiftState = this.stateManager_.hasState(i18n.input.chrome.inputview. + StateType.SHIFT); + + if (upOrDown == hasShiftState) { + // When shift is on, we only take swipe down, otherwise we only + // take swipe up. + return ''; + } + + // The index is based on the characters in order: + // 0: Default + // 1: Shift + // 2: ALTGR + // 3: SHIFT + ALTGR + var index = 0; + if (upOrDown && hasAltGrState) { + index = 3; + } else if (upOrDown && !hasAltGrState) { + index = 1; + } else if (!upOrDown && hasAltGrState) { + index = 2; + } + + var character = index >= this.getChildCount() ? null : + /** @type {!i18n.input.chrome.inputview.elements.content.Character} */ + (this.getChildAt(index)); + if (character && character.isVisible()) { + return character.getContent(); + } + return ''; +}; + + +/** @override */ +CharacterKey.prototype.update = function() { + goog.base(this, 'update'); + + this.pointerConfig.flickerDirection = this.stateManager_.hasState( + i18n.input.chrome.inputview.StateType.SHIFT) ? + i18n.input.chrome.inputview.SwipeDirection.DOWN : + i18n.input.chrome.inputview.SwipeDirection.UP; +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/charactermodel.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/charactermodel.js new file mode 100644 index 0000000..76402c1 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/charactermodel.js @@ -0,0 +1,254 @@ +// 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.CharacterModel'); + +goog.require('i18n.input.chrome.inputview.StateType'); + + + +goog.scope(function() { + + + +/** + * The character model. + * + * @param {string} character The character. + * @param {boolean} belongToLetterKey True if this characters belongs to a + * letter key. + * @param {boolean} hasAltGrCharacterInTheKeyset True if this kind of key has + * altgr character. + * @param {boolean} alwaysRenderAltGrCharacter True if always renders the altgr + * character. + * @param {number} stateType The state type for this character. + * @param {!i18n.input.chrome.inputview.StateManager} stateManager The state + * manager. + * @param {string=} opt_capslockCharacter . + * @constructor + */ +i18n.input.chrome.inputview.elements.content.CharacterModel = function( + character, belongToLetterKey, hasAltGrCharacterInTheKeyset, + alwaysRenderAltGrCharacter, stateType, stateManager, + opt_capslockCharacter) { + + /** + * The character. + * + * @type {string} + * @private + */ + this.character_ = character; + + /** + * The character for the capslock state. + * + * @private {string} + */ + this.capslockCharacter_ = opt_capslockCharacter || ''; + + /** + * Whether this character is belong to a letter key. + * + * @type {boolean} + * @private + */ + this.belongToLetterKey_ = belongToLetterKey; + + /** + * The state. + * + * @type {number} + * @private + */ + this.stateType_ = stateType; + + /** + * The state manager. + * + * @type {!i18n.input.chrome.inputview.StateManager} + * @private + */ + this.stateManager_ = stateManager; + + /** + * Whether to always renders the altgr character.. + * + * @type {boolean} + * @private + */ + this.alwaysRenderAltGrCharacter_ = alwaysRenderAltGrCharacter; + + /** + * True if this key set has altgr character. + * + * @type {boolean} + * @private + */ + this.hasAltGrCharacterInTheKeyset_ = hasAltGrCharacterInTheKeyset; +}; +var CharacterModel = i18n.input.chrome.inputview.elements.content. + CharacterModel; + + +/** + * The alignment type. + * + * @enum {number} + */ +CharacterModel.AlignType = { + CENTER: 0, + CORNER: 1 +}; + + +/** + * The position attributes. + * + * @type {!Array.<!Array.<string>>} + * @private + */ +CharacterModel.CORNERS_ = [ + ['bottom', 'left'], + ['top', 'left'], + ['bottom', 'right'], + ['top', 'right'] +]; + + +/** + * True if this character is highlighed. + * + * @return {boolean} True if the character is highlighted. + */ +CharacterModel.prototype.isHighlighted = function() { + var state = this.stateManager_.getState(); + state = state & (i18n.input.chrome.inputview.StateType.SHIFT | + i18n.input.chrome.inputview.StateType.ALTGR); + return this.stateType_ == state; +}; + + +/** + * True if this character is visible. + * + * @return {boolean} True if the character is visible. + */ +CharacterModel.prototype.isVisible = function() { + if (this.stateType_ == i18n.input.chrome.inputview.StateType.DEFAULT) { + return !this.stateManager_.hasState( + i18n.input.chrome.inputview.StateType.ALTGR) && ( + !this.belongToLetterKey_ || !this.stateManager_.hasState( + i18n.input.chrome.inputview.StateType.SHIFT)); + } + if (this.stateType_ == i18n.input.chrome.inputview.StateType.SHIFT) { + return !this.stateManager_.hasState( + i18n.input.chrome.inputview.StateType.ALTGR) && ( + !this.belongToLetterKey_ || this.stateManager_.hasState( + i18n.input.chrome.inputview.StateType.SHIFT)); + } + if ((this.stateType_ & i18n.input.chrome.inputview.StateType.ALTGR) != 0) { + // AltGr or AltGr+Shift character. + return this.alwaysRenderAltGrCharacter_ || this.stateManager_. + hasState(i18n.input.chrome.inputview.StateType.ALTGR); + } + return false; +}; + + +/** + * Gets the reversed case character. + * + * @return {string} The reversed character + * @private + */ +CharacterModel.prototype.toReversedCase_ = function() { + var reversed; + if (this.character_.toUpperCase() == this.character_) { + reversed = this.character_.toLowerCase(); + } else { + reversed = this.character_.toUpperCase(); + } + return reversed; +}; + + +/** + * Gets the content of this character.. + * + * @return {string} The content. + */ +CharacterModel.prototype.getContent = function() { + if (this.stateManager_.hasState( + i18n.input.chrome.inputview.StateType.CAPSLOCK)) { + return this.capslockCharacter_ ? this.capslockCharacter_ : + this.toReversedCase_(); + } + + return this.character_; +}; + + +/** + * True if align the character in the center horizontally. + * + * @return {boolean} True to align in the center. + */ +CharacterModel.prototype.isHorizontalAlignCenter = function() { + if (this.stateType_ == i18n.input.chrome.inputview.StateType.DEFAULT || + this.stateType_ == i18n.input.chrome.inputview.StateType.SHIFT) { + return !this.alwaysRenderAltGrCharacter_ || + !this.hasAltGrCharacterInTheKeyset_; + } + + return false; +}; + + +/** + * True to align the character in the center vertically. + * + * @return {boolean} True to be in the center. + */ +CharacterModel.prototype.isVerticalAlignCenter = function() { + if (this.stateType_ == i18n.input.chrome.inputview.StateType.DEFAULT || + this.stateType_ == i18n.input.chrome.inputview.StateType.SHIFT) { + return this.belongToLetterKey_; + } + + return false; +}; + + +/** + * Gets the attribute for position. + * + * @return {!Array.<string>} The attributes. + */ +CharacterModel.prototype.getPositionAttribute = function() { + var index; + switch (this.stateType_) { + case i18n.input.chrome.inputview.StateType.DEFAULT: + return CharacterModel.CORNERS_[0]; + case i18n.input.chrome.inputview.StateType.SHIFT: + return CharacterModel.CORNERS_[1]; + case i18n.input.chrome.inputview.StateType.ALTGR: + return CharacterModel.CORNERS_[2]; + default: + return CharacterModel.CORNERS_[3]; + } +}; + + +}); // goog.scope + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/compactkey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/compactkey.js new file mode 100644 index 0000000..aab41f2 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/compactkey.js @@ -0,0 +1,252 @@ +// 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.CompactKey'); + +goog.require('goog.array'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('goog.math.Coordinate'); +goog.require('goog.style'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.MoreKeysShiftOperation'); +goog.require('i18n.input.chrome.inputview.StateType'); +goog.require('i18n.input.chrome.inputview.SwipeDirection'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey'); +goog.require('i18n.input.chrome.inputview.elements.content.GaussianEstimator'); + + + +goog.scope(function() { +var MoreKeysShiftOperation = i18n.input.chrome.inputview.MoreKeysShiftOperation; + + + +/** + * The key to switch between different key set. + * + * @param {string} id The id. + * @param {string} text The text. + * @param {string} hintText The hint text. + * @param {!i18n.input.chrome.inputview.StateManager} stateManager The state + * manager. + * @param {boolean} hasShift True if the compact key has shift.} + * @param {number=} opt_marginLeftPercent The percent of the left margin. + * @param {number=} opt_marginRightPercent The percent of the right margin. + * @param {boolean=} opt_isGrey True if it is grey. + * @param {!Array.<string>=} opt_moreKeys The more keys characters. + * @param {MoreKeysShiftOperation=} opt_moreKeysShiftType + * The type of opearation when the shift key is down. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.content.FunctionalKey} + */ +i18n.input.chrome.inputview.elements.content.CompactKey = function(id, text, + hintText, stateManager, hasShift, opt_marginLeftPercent, + opt_marginRightPercent, opt_isGrey, opt_moreKeys, + opt_moreKeysShiftType, opt_eventTarget) { + goog.base(this, id, i18n.input.chrome.inputview.elements.ElementType. + COMPACT_KEY, text, '', opt_eventTarget); + + /** + * The hint text. + * + * @type {string} + */ + this.hintText = hintText; + + /** + * The left margin. + * + * @type {number} + * @private + */ + this.marginLeftPercent_ = opt_marginLeftPercent || 0; + + /** @private {boolean} */ + this.hasShift_ = hasShift; + + /** + * The right margin. + * + * @type {number} + * @private + */ + this.marginRightPercent_ = opt_marginRightPercent || 0; + + /** + * The state manager. + * + * @type {!i18n.input.chrome.inputview.StateManager} + * @private + */ + this.stateManager_ = stateManager; + + /** + * True if it is grey. + * + * @type {boolean} + * @private + */ + this.isGrey_ = !!opt_isGrey; + + /** + * The more keys array. + * + * @type {!Array.<string>} + */ + this.moreKeys = opt_moreKeys || []; + + /** + * The type of shift operation of moreKeys. + * + * @private {MoreKeysShiftOperation} + */ + this.moreKeysShiftOperation_ = goog.isDef(opt_moreKeysShiftType) ? + opt_moreKeysShiftType : MoreKeysShiftOperation.TO_UPPER_CASE; + + this.pointerConfig.longPressWithPointerUp = true; + this.pointerConfig.flickerDirection = + i18n.input.chrome.inputview.SwipeDirection.UP; + this.pointerConfig.longPressDelay = 500; +}; +goog.inherits(i18n.input.chrome.inputview.elements.content.CompactKey, + i18n.input.chrome.inputview.elements.content.FunctionalKey); +var CompactKey = i18n.input.chrome.inputview.elements.content.CompactKey; + + +/** + * The flickerred character. + * + * @type {string} + */ +CompactKey.prototype.flickerredCharacter = ''; + + +/** @override */ +CompactKey.prototype.createDom = function() { + goog.base(this, 'createDom'); + + goog.dom.classlist.add(this.tableCell, + i18n.input.chrome.inputview.Css.COMPACT_KEY); + if (!this.isGrey_) { + goog.dom.classlist.remove(this.bgElem, + i18n.input.chrome.inputview.Css.SPECIAL_KEY_BG); + } + + if (this.hintText) { + var dom = this.getDomHelper(); + dom.removeChildren(this.tableCell); + var inlineWrap = dom.createDom(goog.dom.TagName.DIV, + i18n.input.chrome.inputview.Css.INLINE_DIV); + dom.appendChild(this.tableCell, inlineWrap); + this.hintTextElem = dom.createDom(goog.dom.TagName.DIV, + i18n.input.chrome.inputview.Css.HINT_TEXT, this.hintText); + dom.appendChild(inlineWrap, this.hintTextElem); + dom.appendChild(inlineWrap, this.textElem); + } +}; + + +/** @override */ +CompactKey.prototype.resize = function(width, height) { + var elem = this.getElement(); + var marginLeft = Math.floor(width * this.marginLeftPercent_); + if (marginLeft > 0) { + marginLeft -= 5; + elem.style.marginLeft = marginLeft + 'px'; + } + var marginRight = Math.floor(width * this.marginRightPercent_); + // TODO: Remove this ugly hack. The default margin right is 10px, we + // need to add the default margin here to make all the keys have the same + // look. + if (marginRight > 0) { + marginRight += 5; + elem.style.marginRight = marginRight + 'px'; + } + + goog.base(this, 'resize', width, height); + + this.topLeftCoordinate = goog.style.getClientPosition(elem); + this.centerCoordinate = new goog.math.Coordinate( + this.topLeftCoordinate.x + this.availableWidth / 2, + this.topLeftCoordinate.y + this.availableHeight / 2); + this.estimator = new i18n.input.chrome.inputview.elements.content. + GaussianEstimator(this.centerCoordinate, + this.stateManager_.covariance.getValue(this.type), + this.availableHeight / this.availableWidth); +}; + + +/** + * Get the active character. It may be upper case |text| when shift is pressed + * or flickerred character when swipe. Note this should replace Compactkey.text + * for compact keys. + */ +CompactKey.prototype.getActiveCharacter = function() { + if (this.flickerredCharacter) { + return this.flickerredCharacter; + } else { + return this.hasShift_ && this.stateManager_.hasState( + i18n.input.chrome.inputview.StateType.SHIFT) ? this.text.toUpperCase() : + this.text; + } +}; + + +/** @override */ +CompactKey.prototype.update = function() { + goog.base(this, 'update'); + + var text = this.hasShift_ && this.stateManager_.hasState( + i18n.input.chrome.inputview.StateType.SHIFT) ? this.text.toUpperCase() : + this.text; + goog.dom.setTextContent(this.textElem, text); +}; + + +/** + * Gets the more characters. + * + * @return {!Array.<string>} The characters. + */ +CompactKey.prototype.getMoreCharacters = function() { + var moreCharacters = goog.array.clone(this.moreKeys); + switch (this.moreKeysShiftOperation_) { + case MoreKeysShiftOperation.TO_UPPER_CASE: + if (this.getActiveCharacter().toLowerCase() != + this.getActiveCharacter()) { + for (var i = 0; i < this.moreKeys.length; i++) { + moreCharacters[i] = this.moreKeys[i].toUpperCase(); + } + goog.array.removeDuplicates(moreCharacters); + } + return moreCharacters; + case MoreKeysShiftOperation.TO_LOWER_CASE: + if (this.hasShift_ && this.stateManager_.hasState( + i18n.input.chrome.inputview.StateType.SHIFT)) { + for (var i = 0; i < this.moreKeys.length; i++) { + moreCharacters[i] = this.moreKeys[i].toLowerCase(); + } + goog.array.removeDuplicates(moreCharacters); + } + return moreCharacters; + case MoreKeysShiftOperation.STABLE: + break; + } + return moreCharacters; +}; +}); // goog.scope + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/emojikey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/emojikey.js new file mode 100644 index 0000000..60f32c88 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/emojikey.js @@ -0,0 +1,117 @@ +// 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.EmojiKey'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey'); +goog.require('i18n.input.chrome.message.Name'); +goog.require('i18n.input.chrome.message.Type'); + +goog.scope(function() { + +var Type = i18n.input.chrome.message.Type; +var Name = i18n.input.chrome.message.Name; + + + +/** + * The emoji key + * + * @param {string} id The id. + * @param {!i18n.input.chrome.inputview.elements.ElementType} type The element + * type. + * @param {string} text The text. + * @param {string} iconCssClass The css class for the icon. + * @param {boolean} isEmoticon Wether it is an emoticon. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.content.FunctionalKey} + */ +i18n.input.chrome.inputview.elements.content.EmojiKey = function(id, type, + text, iconCssClass, isEmoticon, opt_eventTarget) { + i18n.input.chrome.inputview.elements.content.EmojiKey.base( + this, 'constructor', id, type, text, '', opt_eventTarget); + + /** + * Wether it is an emoticon. + * + * @private {boolean} + */ + this.isEmoticon_ = isEmoticon; + + this.pointerConfig.stopEventPropagation = false; + + this.pointerConfig.dblClick = true; + this.pointerConfig.longPressWithPointerUp = true; + this.pointerConfig.longPressDelay = 200; + +}; +goog.inherits(i18n.input.chrome.inputview.elements.content.EmojiKey, + i18n.input.chrome.inputview.elements.content.FunctionalKey); +var EmojiKey = i18n.input.chrome.inputview.elements.content.EmojiKey; + + +/** @override */ +EmojiKey.prototype.createDom = function() { + goog.base(this, 'createDom'); + var dom = this.getDomHelper(); + var elem = this.getElement(); + if (!this.textElem) { + this.textElem = dom.createDom(goog.dom.TagName.DIV, + i18n.input.chrome.inputview.Css.SPECIAL_KEY_NAME, this.text); + dom.appendChild(this.tableCell, this.textElem); + } + // Special size for emojitcon. + if (this.isEmoticon_) { + this.textElem.style.fontSize = '20px'; + } + goog.dom.classlist.remove(elem, i18n.input.chrome.inputview.Css.SOFT_KEY); + goog.dom.classlist.remove(this.bgElem, + i18n.input.chrome.inputview.Css.SPECIAL_KEY_BG); + goog.dom.classlist.add(this.bgElem, + i18n.input.chrome.inputview.Css.EMOJI_KEY); +}; + + +/** @override */ +EmojiKey.prototype.setHighlighted = function(highlight) { + if (highlight) { + goog.dom.classlist.add(this.bgElem, + i18n.input.chrome.inputview.Css.EMOJI_KEY_HIGHLIGHT); + } else { + goog.dom.classlist.remove(this.bgElem, + i18n.input.chrome.inputview.Css.EMOJI_KEY_HIGHLIGHT); + } +}; + + +/** @override */ +EmojiKey.prototype.update = function() { + goog.base(this, 'update'); + goog.dom.setTextContent(this.textElem, this.text); +}; + + +/** + * Update the emoji's text + * + * @param {string} text The new text. + */ +EmojiKey.prototype.updateText = function(text) { + this.text = text; + goog.dom.setTextContent(this.textElem, text); +}; +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/emojiview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/emojiview.js new file mode 100644 index 0000000..239650b --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/emojiview.js @@ -0,0 +1,423 @@ +// 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.EmojiView'); + +goog.require('goog.array'); +goog.require('goog.dom.classlist'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.SpecNodeName'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.inputview.elements.content.KeysetView'); +goog.require('i18n.input.chrome.inputview.events.EventType'); +goog.require('i18n.input.chrome.inputview.handler.PointerHandler'); + + +goog.scope(function() { +var ElementType = i18n.input.chrome.inputview.elements.ElementType; +var EventType = i18n.input.chrome.inputview.events.EventType; +var KeysetView = i18n.input.chrome.inputview.elements.content.KeysetView; +var PointerHandler = i18n.input.chrome.inputview.handler.PointerHandler; +var Css = i18n.input.chrome.inputview.Css; +var SpecNodeName = i18n.input.chrome.inputview.SpecNodeName; +var ExtendedLayout = i18n.input.chrome.inputview.elements.layout.ExtendedLayout; +var PageIndicator = i18n.input.chrome.inputview.elements.content.PageIndicator; + + + +/** + * The emoji view. + * + * @param {!Object} keyData The data includes soft key definition and key + * mapping. + * @param {!Object} layoutData The layout definition. + * @param {string} keyboardCode The keyboard code. + * @param {string} languageCode The language code. + * @param {!i18n.input.chrome.inputview.Model} model The model. + * @param {string} name The Input Tool name. + * @param {!goog.events.EventTarget=} opt_eventTarget . + * @param {i18n.input.chrome.inputview.Adapter=} opt_adapter . + * @constructor + * @extends {KeysetView} + */ +i18n.input.chrome.inputview.elements.content.EmojiView = function(keyData, + layoutData, keyboardCode, languageCode, model, name, opt_eventTarget, + opt_adapter) { + i18n.input.chrome.inputview.elements.content.EmojiView.base(this, + 'constructor', keyData, layoutData, keyboardCode, languageCode, model, + name, opt_eventTarget, opt_adapter); + + /** + * The number of keys per emoji page. + * + * @private {number} + */ + this.keysPerPage_ = 27; + + /** + * The number of tabbar keys. + * + * @private {number} + */ + this.totalTabbars_ = keyData[SpecNodeName.TEXT].length; + + /** + * The first page offset of each category. + * + * @private {!Array.<number>} + */ + this.pageOffsets_ = []; + + /** + * The number of pages for each category. + * + * @private {!Array.<number>} + */ + this.pagesInCategory_ = []; + + // Calculate the emojiPageoffset_ and totalPages_ according to keydata. + var pageNum = 0; + for (var i = 0, len = keyData[SpecNodeName.TEXT].length; i < len; ++i) { + this.pageOffsets_.push(pageNum); + pageNum += Math.ceil( + keyData[SpecNodeName.TEXT][i].length / this.keysPerPage_); + } + + /** + * The category of each emoji page. + * + * @private {!Array.<number>} + */ + this.pageToCategory_ = []; + + // Calculate the pageToCategory_ according to keydata. + for (var i = 0, len = keyData[SpecNodeName.TEXT].length; i < len; ++i) { + var lenJ = Math.ceil( + keyData[SpecNodeName.TEXT][i].length / this.keysPerPage_); + for (var j = 0; j < lenJ; ++j) { + this.pageToCategory_.push(i); + } + this.pagesInCategory_.push(lenJ); + } + + /** + * The list of recent used emoji. + * + * @private {Array.<string>} + */ + this.recentEmojiList_ = []; + + /** + * The emoji keys on the recent page. + * + * @private {Array.<i18n.input.chrome.inputview.elements.content.EmojiKey>} + */ + this.recentEmojiKeys_ = []; + + /** + * The tabbars of the emoji view. + * + * @private {Array.<i18n.input.chrome.inputview.elements.content.TabBarKey>} + */ + this.tabbarKeys_ = []; +}; +var EmojiView = i18n.input.chrome.inputview.elements.content.EmojiView; +goog.inherits(EmojiView, KeysetView); + + +/** + * The emoji rows of the emoji slider. + * + * @private {!i18n.input.chrome.inputview.elements.layout.ExtendedLayout} + */ +EmojiView.prototype.emojiRows_; + + +/** + * The indicator of the emoji page index. + * + * @private {!i18n.input.chrome.inputview.elements.content.PageIndicator} + */ +EmojiView.prototype.pageIndicator_; + + +/** + * Whether it is a drag event. + * + * @type {boolean} + */ +EmojiView.prototype.isDragging = false; + + +/** + * Whether it is a drag event. + * + * @private {number} + */ +EmojiView.prototype.categoryID_ = 0; + + +/** + * The timestamp of the last pointer down event. + * + * @private {number} + */ +EmojiView.prototype.pointerDownTimeStamp_ = 0; + + +/** + * The drag distance of a drag event. + * + * @private {number} + */ +EmojiView.prototype.dragDistance_ = 0; + + +/** + * The maximal required time interval for quick emoji page swipe in ms. + * + * @private {number} + */ +EmojiView.EMOJI_DRAG_INTERVAL_ = 300; + + +/** + * The minimal required drag distance for quick emoji page swipe in px. + * + * @private {number} + */ +EmojiView.EMOJI_DRAG_DISTANCE_ = 60; + + +/** @private {!PointerHandler} */ +EmojiView.prototype.pointerHandler_; + + +/** @override */ +EmojiView.prototype.createDom = function() { + goog.base(this, 'createDom'); + var elem = this.getElement(); + if (elem) { + this.pointerHandler_ = new PointerHandler(elem); + } + this.getHandler(). + listen(this.pointerHandler_, EventType.POINTER_DOWN, + this.onPointerDown_). + listen(this.pointerHandler_, EventType.POINTER_UP, this.onPointerUp_). + listen(this.pointerHandler_, EventType.DRAG, this.onDragEvent_). + listen(this.pointerHandler_, EventType.LONG_PRESS, this.onLongPress_); + this.emojiRows_ = + /** @type {!ExtendedLayout} */ (this.getChildViewById('emojiRows')); + this.pageIndicator_ = + /** @type {!PageIndicator} */ + (this.getChildViewById('indicator-background')); + for (var i = 0; i < this.keysPerPage_; i++) { + this.recentEmojiKeys_.push( + /** @type {!i18n.input.chrome.inputview.elements.content.EmojiKey} */ + (this.getChildViewById('emojikey' + i))); + } + for (var i = 0; i < this.totalTabbars_; i++) { + this.tabbarKeys_.push( + /** @type {!i18n.input.chrome.inputview.elements.content.TabBarKey} */ + (this.getChildViewById('Tabbar' + i))); + } +}; + + +/** + * Handles the pointer down event. + * + * @param {!i18n.input.chrome.inputview.events.PointerEvent} e . + * @private + */ +EmojiView.prototype.onPointerDown_ = function(e) { + var view = e.view; + if (view.type == ElementType.EMOJI_KEY) { + this.pointerDownTimeStamp_ = e.timestamp; + this.dragDistance_ = 0; + return; + } +}; + + +/** + * Handles the pointer up event. + * + * @param {!i18n.input.chrome.inputview.events.PointerEvent} e . + * @private + */ +EmojiView.prototype.onPointerUp_ = function(e) { + var view = e.view; + switch (view.type) { + case ElementType.EMOJI_KEY: + if (this.isDragging) { + var interval = e.timestamp - this.pointerDownTimeStamp_; + if (interval < EmojiView.EMOJI_DRAG_INTERVAL_ && + Math.abs(this.dragDistance_) >= EmojiView.EMOJI_DRAG_DISTANCE_) { + this.adjustMarginLeft_(this.dragDistance_); + } else { + this.adjustMarginLeft_(); + } + + this.isDragging = false; + } else if (view.text != '') { + this.setRecentEmoji_(view.text); + } + this.update(); + return; + case ElementType.TAB_BAR_KEY: + this.updateCategory_(view); + this.updateTabbarBorder_(); + return; + } +}; + + +/** + * Handles the drag event. + * + * @param {!i18n.input.chrome.inputview.events.PointerEvent} e . + * @private + */ +EmojiView.prototype.onDragEvent_ = function(e) { + var view = e.view; + this.isDragging = true; + if (view.type == ElementType.EMOJI_KEY) { + this.setEmojiMarginLeft_(e.deltaX); + this.dragDistance_ += e.deltaX; + } +}; + + +/** + * Handles the long press event. + * + * @param {!i18n.input.chrome.inputview.events.PointerEvent} e . + * @private + */ +EmojiView.prototype.onLongPress_ = function(e) { + var view = e.view; + view.setHighlighted(this.isDragging == false && view.text != ''); +}; + + +/** @override */ +EmojiView.prototype.disposeInternal = function() { + goog.dispose(this.pointerHandler_); + + goog.base(this, 'disposeInternal'); +}; + + +/** + * Set the margin left of the emoji slider. + * + * @param {number} deltaX The margin left value. + * @private + */ +EmojiView.prototype.setEmojiMarginLeft_ = function(deltaX) { + this.emojiRows_.slide(deltaX); + this.pageIndicator_.slide(-deltaX, + this.pagesInCategory_[this.categoryID_]); +}; + + +/** + * Update the current emoji category. + * + * @param {i18n.input.chrome.inputview.elements.Element} view The view. + * @private + */ +EmojiView.prototype.updateCategory_ = function(view) { + this.categoryID_ = view.toCategory; + this.emojiRows_.updateCategory(this.pageOffsets_[this.categoryID_]); + this.pageIndicator_.gotoPage(0, + this.pagesInCategory_[this.categoryID_]); + this.updateTabbarBorder_(); +}; + + +/** + * Adjust the margin left to the nearest page. + * + * @param {number=} opt_distance The distance to adjust to. + * @private + */ +EmojiView.prototype.adjustMarginLeft_ = function(opt_distance) { + var pageNum = this.emojiRows_.adjustMarginLeft(opt_distance); + this.categoryID_ = this.pageToCategory_[pageNum]; + this.pageIndicator_.gotoPage( + pageNum - this.pageOffsets_[this.categoryID_], + this.pagesInCategory_[this.categoryID_]); + this.updateTabbarBorder_(); + +}; + + +/** + * Clear all the states for the emoji. + * + */ +EmojiView.prototype.clearEmojiStates = function() { + this.categoryID_ = 1; + this.emojiRows_.updateCategory(1); + this.pageIndicator_.gotoPage(0, this.pagesInCategory_[1]); + this.updateTabbarBorder_(); +}; + + +/** + * Sets the recent emoji. + * + * @param {string} text The recent emoji text. + * @private + */ +EmojiView.prototype.setRecentEmoji_ = function(text) { + goog.array.insertAt(this.recentEmojiList_, text, 0); + goog.array.removeDuplicates(this.recentEmojiList_); + var len = this.recentEmojiList_.length; + for (var i = 0; i < this.keysPerPage_; i++) { + var newText = i < len ? this.recentEmojiList_[i] : ''; + this.recentEmojiKeys_[i].updateText(newText); + } +}; + + +/** + * Update the tabbar's border. + * + * @private + */ +EmojiView.prototype.updateTabbarBorder_ = function() { + for (var i = 0, len = this.totalTabbars_; i < len; i++) { + this.tabbarKeys_[i].updateBorder(this.categoryID_); + } +}; + + +/** @override */ +EmojiView.prototype.activate = function(rawKeyset) { + this.adapter.setEmojiInputToolCode(); + goog.dom.classlist.add(this.getElement().parentElement.parentElement, + Css.EMOJI); + this.clearEmojiStates(); +}; + + +/** @override */ +EmojiView.prototype.deactivate = function(rawKeyset) { + this.adapter.unsetEmojiInputToolCode(); + goog.dom.classlist.remove(this.getElement().parentElement.parentElement, + Css.EMOJI); +}; +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/enswitcherkey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/enswitcherkey.js new file mode 100644 index 0000000..7ca84e7 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/enswitcherkey.js @@ -0,0 +1,102 @@ +// 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.EnSwitcherKey'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey'); +goog.require('i18n.input.chrome.message.Name'); +goog.require('i18n.input.chrome.message.Type'); + + + +goog.scope(function() { + +var Type = i18n.input.chrome.message.Type; +var Name = i18n.input.chrome.message.Name; + +/** + * The switcher key to switch to engish. + * + * @param {string} id The id. + * @param {!i18n.input.chrome.inputview.elements.ElementType} type The element + * type. + * @param {string} text The text. + * @param {i18n.input.chrome.inputview.Css} iconCssClass The css for the icon. + * @param {!i18n.input.chrome.inputview.StateManager} stateManager The state + * manager. + * @param {i18n.input.chrome.inputview.Css} defaultCss + * The Css for the default icon. + * @param {i18n.input.chrome.inputview.Css} englishCss + * The Css for the english icon. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.content.FunctionalKey} + */ +i18n.input.chrome.inputview.elements.content.EnSwitcherKey = function(id, type, + text, iconCssClass, stateManager, defaultCss, englishCss) { + i18n.input.chrome.inputview.elements.content.EnSwitcherKey.base( + this, 'constructor', id, type, text, ''); + + /** + * The current icon css. + * + * @private {i18n.input.chrome.inputview.Css} + */ + this.currentIconCss_ = defaultCss; + + /** + * The state manager. + * + * @private {i18n.input.chrome.inputview.StateManager} + */ + this.stateManager_ = stateManager; + + /** + * The default iconCss for a given keyset. + * + * @private {i18n.input.chrome.inputview.Css} + */ + this.defaultIconCss_ = defaultCss; + + /** + * The iconCss for the english mode. + * + * @private {i18n.input.chrome.inputview.Css} + */ + this.enIconCss_ = englishCss; +}; +goog.inherits(i18n.input.chrome.inputview.elements.content.EnSwitcherKey, + i18n.input.chrome.inputview.elements.content.FunctionalKey); +var EnSwitcherKey = i18n.input.chrome.inputview.elements.content.EnSwitcherKey; + + +/** @override */ +EnSwitcherKey.prototype.createDom = function() { + goog.base(this, 'createDom'); + var dom = this.getDomHelper(); + this.iconElem = dom.createDom(goog.dom.TagName.DIV, this.currentIconCss_); + dom.appendChild(this.tableCell, this.iconElem); +}; + + +/** @override */ +EnSwitcherKey.prototype.update = function() { + goog.base(this, 'update'); + var isEnMode = this.stateManager_.isEnMode; + goog.dom.classlist.remove(this.iconElem, this.currentIconCss_); + this.currentIconCss_ = isEnMode ? this.enIconCss_ : this.defaultIconCss_; + goog.dom.classlist.add(this.iconElem, this.currentIconCss_); +}; +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/expandedcandidateview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/expandedcandidateview.js new file mode 100644 index 0000000..8c56df1 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/expandedcandidateview.js @@ -0,0 +1,326 @@ +// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved. +// limitations under the License. +// See the License for the specific language governing permissions and +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// distributed under the License is distributed on an "AS-IS" BASIS, +// Unless required by applicable law or agreed to in writing, software +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// You may obtain a copy of the License at +// you may not use this file except in compliance with the License. +// Licensed under the Apache License, Version 2.0 (the "License"); +// +goog.provide('i18n.input.chrome.inputview.elements.content.ExpandedCandidateView'); + +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('goog.style'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.Element'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.inputview.elements.content.Candidate'); +goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey'); + + +goog.scope(function() { + +var Css = i18n.input.chrome.inputview.Css; +var TagName = goog.dom.TagName; +var Candidate = i18n.input.chrome.inputview.elements.content.Candidate; +var Type = i18n.input.chrome.inputview.elements.content.Candidate.Type; +var ElementType = i18n.input.chrome.inputview.elements.ElementType; +var FunctionalKey = i18n.input.chrome.inputview.elements.content.FunctionalKey; + + + +/** + * The expanded candidate view. + * + * @param {!goog.events.EventTarget=} opt_eventTarget . + * @constructor + * @extends {i18n.input.chrome.inputview.elements.Element} + */ +i18n.input.chrome.inputview.elements.content.ExpandedCandidateView = function( + opt_eventTarget) { + goog.base(this, 'expandedCandidateView', ElementType. + EXPANDED_CANDIDATE_VIEW, opt_eventTarget); + + /** + * The four lines. + * + * @private {!Array.<!Element>} + */ + this.lines_ = []; + + /** + * The functional keys at the right. + * + * @private {!Array.<FunctionalKey>} + */ + this.keys_ = []; + + /** + * Key: page index. + * Value: candidate start index. + * + * @private {!Object.<number, number>} + */ + this.pageIndexMap_ = {}; +}; +var ExpandedCandidateView = i18n.input.chrome.inputview.elements.content. + ExpandedCandidateView; +goog.inherits(ExpandedCandidateView, + i18n.input.chrome.inputview.elements.Element); + + +/** + * The index of the key. + * + * @enum {number} + */ +ExpandedCandidateView.KeyIndex = { + BACKSPACE: 0, + ENTER: 1, + PAGE_UP: 2, + PAGE_DOWN: 3 +}; + + +/** + * The state of the expanded candidate view. + * + * @enum {number} + */ +ExpandedCandidateView.State = { + NONE: 0, + COMPLETION_CORRECTION: 1, + PREDICTION: 2 +}; + + +/** + * The state of the expanded candidate view. + * + * @type {ExpandedCandidateView.State} + */ +ExpandedCandidateView.prototype.state = ExpandedCandidateView.State.NONE; + + +/** + * The current page index. + * + * @private {number} + */ +ExpandedCandidateView.prototype.pageIndex_ = 0; + + +/** @private {number} */ +ExpandedCandidateView.prototype.candidateStartIndex_ = 0; + + +/** @private {!Array.<!Object>} */ +ExpandedCandidateView.prototype.candidates_; + + +/** + * The padding between candidates. + * + * @private {number} + */ +ExpandedCandidateView.RIGHT_KEY_WIDTH_ = 120; + + +/** + * How many cells divided in one line. + * + * @type {number} + * @private + */ +ExpandedCandidateView.CELLS_PER_LINE_ = 10; + + +/** @private {number} */ +ExpandedCandidateView.LINES_ = 4; + + +/** @private {number} */ +ExpandedCandidateView.prototype.widthPerCell_ = 0; + + +/** @private {number} */ +ExpandedCandidateView.prototype.heightPerCell_ = 0; + + +/** @override */ +ExpandedCandidateView.prototype.createDom = function() { + goog.base(this, 'createDom'); + + var dom = this.getDomHelper(); + var line = this.createCandidateLine_(true); + this.createKey_(ElementType.BACKSPACE_KEY, Css.BACKSPACE_ICON); + + line = this.createCandidateLine_(false); + this.createKey_(ElementType.ENTER_KEY, Css.ENTER_ICON); + + line = this.createCandidateLine_(false); + this.createKey_(ElementType.CANDIDATES_PAGE_UP, Css.PAGE_UP_ICON); + + line = this.createCandidateLine_(false); + this.createKey_(ElementType.CANDIDATES_PAGE_DOWN, Css.PAGE_DOWN_ICON); +}; + + +/** + * Creates a line for the candidates. + * + * @param {boolean} isTopLine . + * @private + */ +ExpandedCandidateView.prototype.createCandidateLine_ = function(isTopLine) { + var dom = this.getDomHelper(); + var line = dom.createDom(TagName.DIV, [Css.CANDIDATE_INTER_CONTAINER, + Css.CANDIDATES_LINE]); + if (isTopLine) { + goog.dom.classlist.add(line, Css.CANDIDATES_TOP_LINE); + } + dom.appendChild(this.getElement(), line); + this.lines_.push(line); +}; + + +/** + * Creates the right functional key. + * + * @param {ElementType} type . + * @param {string} iconCss . + * @private + */ +ExpandedCandidateView.prototype.createKey_ = function(type, iconCss) { + var key = new FunctionalKey('', type, '', iconCss, this); + key.render(this.getElement()); + goog.dom.classlist.add(key.getElement(), Css.INLINE_DIV); + this.keys_.push(key); + return key; +}; + + +/** + * Pages up to show more candidates. + */ +ExpandedCandidateView.prototype.pageUp = function() { + if (this.pageIndex_ > 0) { + this.pageIndex_--; + this.showCandidates(this.candidates_, this.pageIndexMap_[this.pageIndex_]); + } +}; + + +/** + * Pages down to the previous candidate page. + */ +ExpandedCandidateView.prototype.pageDown = function() { + if (this.candidates_.length > this.candidateStartIndex_) { + this.pageIndex_++; + this.showCandidates(this.candidates_, this.candidateStartIndex_); + } +}; + + +/** + * Closes this view. + */ +ExpandedCandidateView.prototype.close = function() { + this.candidates_ = []; + this.pageIndex_ = 0; + this.pageIndexMap_ = {}; + this.candidateStartIndex_ = 0; + this.setVisible(false); +}; + + +/** + * Shows the candidates in expanded view. + * + * @param {!Array.<!Object>} candidates . + * @param {number} start . + */ +ExpandedCandidateView.prototype.showCandidates = function(candidates, + start) { + this.setVisible(true); + var dom = this.getDomHelper(); + for (var i = 0; i < this.lines_.length; i++) { + dom.removeChildren(this.lines_[i]); + } + + this.pageIndexMap_[this.pageIndex_] = start; + this.candidates_ = candidates; + var lineIndex = 0; + var line = this.lines_[lineIndex]; + var cellsInLine = ExpandedCandidateView.CELLS_PER_LINE_; + var previousCandidate = null; + var previousCandidateWidth = 0; + var i; + for (i = start; i < candidates.length; i++) { + var candidate = candidates[i]; + var candidateElem = new Candidate(String(i), candidate, Type.CANDIDATE, + this.heightPerCell_, false, undefined, this); + candidateElem.render(line); + var size = goog.style.getSize(candidateElem.getElement()); + var cells = Math.ceil(size.width / this.widthPerCell_); + if (cellsInLine < cells) { + // If there is not enough cells, just put this candidate to a new line + // and give the rest cells to the last candidate. + line.removeChild(candidateElem.getElement()); + goog.style.setSize(previousCandidate.getElement(), cellsInLine * + this.widthPerCell_ + previousCandidateWidth, this.heightPerCell_); + lineIndex++; + if (lineIndex == ExpandedCandidateView.LINES_) { + break; + } + cellsInLine = ExpandedCandidateView.CELLS_PER_LINE_ - cells; + line = this.lines_[lineIndex]; + dom.appendChild(line, candidateElem.getElement()); + } else { + cellsInLine -= cells; + } + var width = cells * this.widthPerCell_; + goog.style.setSize(candidateElem.getElement(), width, this.heightPerCell_); + + if (cellsInLine == 0) { + lineIndex++; + if (lineIndex == ExpandedCandidateView.LINES_) { + break; + } + cellsInLine = ExpandedCandidateView.CELLS_PER_LINE_; + line = this.lines_[lineIndex]; + } + + candidateElem.setVisible(true); + previousCandidateWidth = width; + previousCandidate = candidateElem; + } + this.candidateStartIndex_ = i; +}; + + +/** @override */ +ExpandedCandidateView.prototype.resize = function(width, height) { + goog.base(this, 'resize', width, height); + + goog.style.setSize(this.getElement(), width, height); + this.widthPerCell_ = Math.floor((width - ExpandedCandidateView. + RIGHT_KEY_WIDTH_) / ExpandedCandidateView.CELLS_PER_LINE_); + this.heightPerCell_ = height / ExpandedCandidateView.LINES_; + for (var i = 0; i < this.lines_.length; i++) { + var line = this.lines_[i]; + goog.style.setSize(line, Math.floor(width - + ExpandedCandidateView.RIGHT_KEY_WIDTH_), this.heightPerCell_); + } + for (var i = 0; i < this.keys_.length; i++) { + var key = this.keys_[i]; + key.resize(ExpandedCandidateView.RIGHT_KEY_WIDTH_, this.heightPerCell_); + } +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/functionalkey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/functionalkey.js new file mode 100644 index 0000000..8c63c13 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/functionalkey.js @@ -0,0 +1,197 @@ +// 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.FunctionalKey'); + +goog.require('goog.a11y.aria'); +goog.require('goog.a11y.aria.State'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.inputview.elements.content.SoftKey'); + + +goog.scope(function() { +var ElementType = i18n.input.chrome.inputview.elements.ElementType; + + + +/** + * The functional key, it could be modifier keys or keys like + * backspace, tab, etc. + * + * @param {string} id The id. + * @param {!i18n.input.chrome.inputview.elements.ElementType} type The element + * type. + * @param {string} text The text. + * @param {string} iconCssClass The css class for the icon. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @param {string=} opt_textCssClass The css class for the text. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.content.SoftKey} + */ +i18n.input.chrome.inputview.elements.content.FunctionalKey = function(id, type, + text, iconCssClass, opt_eventTarget, opt_textCssClass) { + goog.base(this, id, type, opt_eventTarget); + + /** + * The text in the key. + * + * @type {string} + */ + this.text = text; + + /** + * The css class for the icon. + * + * @type {string} + * @private + */ + this.iconCssClass_ = iconCssClass; + + /** + * The css class for the text. + * + * @type {string} + * @private + */ + this.textCssClass_ = opt_textCssClass || ''; +}; +goog.inherits(i18n.input.chrome.inputview.elements.content.FunctionalKey, + i18n.input.chrome.inputview.elements.content.SoftKey); +var FunctionalKey = i18n.input.chrome.inputview.elements.content.FunctionalKey; + + +/** + * The table cell. + * + * @type {!Element} + */ +FunctionalKey.prototype.tableCell; + + +/** + * The element contains the text. + * + * @type {!Element} + */ +FunctionalKey.prototype.textElem; + + +/** + * The element contains the icon. + * + * @type {!Element} + */ +FunctionalKey.prototype.iconElem; + + +/** @override */ +FunctionalKey.prototype.createDom = function() { + goog.base(this, 'createDom'); + + var dom = this.getDomHelper(); + var elem = this.getElement(); + this.bgElem = dom.createDom(goog.dom.TagName.DIV, + i18n.input.chrome.inputview.Css.SPECIAL_KEY_BG); + dom.appendChild(elem, this.bgElem); + this.tableCell = dom.createDom(goog.dom.TagName.DIV); + goog.dom.classlist.add(this.tableCell, + i18n.input.chrome.inputview.Css.MODIFIER); + if (this.text) { + this.textElem = dom.createDom(goog.dom.TagName.DIV, + i18n.input.chrome.inputview.Css.SPECIAL_KEY_NAME, this.text); + if (this.textCssClass_) { + goog.dom.classlist.add(this.textElem, this.textCssClass_); + } + dom.appendChild(this.tableCell, this.textElem); + } + if (this.iconCssClass_) { + this.iconElem = dom.createDom(goog.dom.TagName.DIV, + this.iconCssClass_); + dom.appendChild(this.tableCell, this.iconElem); + } + dom.appendChild(this.bgElem, this.tableCell); + + this.setAriaLabel(this.getChromeVoxMessage()); +}; + + +/** @override */ +FunctionalKey.prototype.resize = function(width, + height) { + goog.base(this, 'resize', width, height); + + this.tableCell.style.width = this.availableWidth + 'px'; + this.tableCell.style.height = this.availableHeight + 'px'; +}; + + +/** @override */ +FunctionalKey.prototype.setHighlighted = function( + highlight) { + if (highlight) { + goog.dom.classlist.add(this.bgElem, + i18n.input.chrome.inputview.Css.SPECIAL_KEY_HIGHLIGHT); + } else { + goog.dom.classlist.remove(this.bgElem, + i18n.input.chrome.inputview.Css.SPECIAL_KEY_HIGHLIGHT); + } +}; + + +/** + * Gets the chrome vox message. + * + * @return {string} . + */ +FunctionalKey.prototype.getChromeVoxMessage = function() { + switch (this.type) { + case ElementType.BACKSPACE_KEY: + return chrome.i18n.getMessage('BACKSPACE'); + case ElementType.ENTER_KEY: + return chrome.i18n.getMessage('ENTER'); + case ElementType.TAB_KEY: + return chrome.i18n.getMessage('TAB'); + case ElementType.ARROW_UP: + return chrome.i18n.getMessage('UP_ARROW'); + case ElementType.ARROW_DOWN: + return chrome.i18n.getMessage('DOWN_ARROW'); + case ElementType.ARROW_LEFT: + return chrome.i18n.getMessage('LEFT_ARROW'); + case ElementType.ARROW_RIGHT: + return chrome.i18n.getMessage('RIGHT_ARROW'); + case ElementType.HIDE_KEYBOARD_KEY: + return chrome.i18n.getMessage('HIDE_KEYBOARD'); + case ElementType.GLOBE_KEY: + return chrome.i18n.getMessage('GLOBE'); + } + return ''; +}; + + +/** + * Sets the aria label. + * + * @param {string} label . + */ +FunctionalKey.prototype.setAriaLabel = function(label) { + var elem = this.textElem || this.iconElem; + if (elem) { + goog.a11y.aria.setState(elem, goog.a11y.aria.State.LABEL, label); + } +}; + +}); // goog.scope + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/gaussianestimator.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/gaussianestimator.js new file mode 100644 index 0000000..9704c63 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/gaussianestimator.js @@ -0,0 +1,87 @@ +// 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.GaussianEstimator'); + + +goog.scope(function() { + +/** + * A tool class to calculate probability with Gaussian distribution. + * Gaussian(x, y) = Norm * exp (-(1/2) * ((x - centerX) ^ 2 * CinvX + (y - + * centerY) ^ 2 * CinvY)) + * where + * CinvX = 1 / (AmplitudeX * Covariance) + * CinvY = 1 / (AmplitudeY * Covariance) + * Norm = 1 / (2 * PI) * Sqrt(CinX * CinY) + * LogGaussian(x, y) = LogNorm + (-1/2) * ((x - centerX) ^ 2 * CinvX + * + (y - centerY) ^ 2 * CinvY)) + * In this class we assumes amplitude Y is normalized to 1, so + * amplitude X is real amplitude X relative to amplitude Y. + * + * @param {!goog.math.Coordinate} center . + * @param {number} covariance . + * @param {!number} amplitude Amplitude on dimension X of the distribution. The + * estimator assumes amplitude on dimension Y is 1, so this value is real + * amplitude X relative to amplitude Y. + * @constructor + */ +i18n.input.chrome.inputview.elements.content.GaussianEstimator = function( + center, covariance, amplitude) { + /** + * The center point. + * + * @private {!goog.math.Coordinate} + */ + this.center_ = center; + + /** + * The CinvX. + * + * @private {number} + */ + this.cinvX_ = 1 / (amplitude * covariance); + + /** + * The CinvY. + * + * @private {number} + */ + this.cinvY_ = 1 / covariance; + + /** + * The Norm in log space. + * + * @private {number} + */ + this.logNorm_ = Math.log(1 / (2 * Math.PI * Math.sqrt(amplitude * + covariance * covariance))); +}; +var GaussianEstimator = i18n.input.chrome.inputview.elements.content. + GaussianEstimator; + + +/** + * Estimates the possibility in log space. + * + * @param {number} x . + * @param {number} y . + */ +GaussianEstimator.prototype.estimateInLogSpace = function(x, y) { + var dx = x - this.center_.x; + var dy = y - this.center_.y; + var exponent = this.cinvX_ * dx * dx + this.cinvY_ * dy * dy; + return this.logNorm_ + (-0.5) * exponent; +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/handwritingview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/handwritingview.js new file mode 100644 index 0000000..50194bf --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/handwritingview.js @@ -0,0 +1,92 @@ +// 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.HandwritingView'); + +goog.require('goog.dom.classlist'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.content.KeysetView'); + + +goog.scope(function() { +var Css = i18n.input.chrome.inputview.Css; +var KeysetView = i18n.input.chrome.inputview.elements.content.KeysetView; + + + +/** + * The handwriting view. + * + * @param {!Object} keyData The data includes soft key definition and key + * mapping. + * @param {!Object} layoutData The layout definition. + * @param {string} keyboardCode The keyboard code. + * @param {string} languageCode The language code. + * @param {!i18n.input.chrome.inputview.Model} model The model. + * @param {string} name The Input Tool name. + * @param {!goog.events.EventTarget=} opt_eventTarget . + * @param {i18n.input.chrome.inputview.Adapter=} opt_adapter . + * @constructor + * @extends {KeysetView} + */ +i18n.input.chrome.inputview.elements.content.HandwritingView = function(keyData, + layoutData, keyboardCode, languageCode, model, name, opt_eventTarget, + opt_adapter) { + i18n.input.chrome.inputview.elements.content.HandwritingView.base(this, + 'constructor', keyData, layoutData, keyboardCode, languageCode, model, + name, opt_eventTarget, opt_adapter); +}; +var HandwritingView = i18n.input.chrome.inputview.elements.content. + HandwritingView; +goog.inherits(HandwritingView, KeysetView); + + +/** + * The handwriting input tool code suffix. + * + * @const {string} + * @private + */ +HandwritingView.HANDWRITING_CODE_SUFFIX_ = '-t-i0-handwrit'; + + +/** @override */ +HandwritingView.prototype.activate = function(rawKeyset) { + this.adapter.setInputToolCode(this.languageCode.split(/_|-/)[0] + + HandwritingView.HANDWRITING_CODE_SUFFIX_); + goog.dom.classlist.add(this.getElement().parentElement.parentElement, + Css.HANDWRITING); + // Clears stroke when switches keyboard. + if (this.canvasView.hasStrokesOnCanvas()) { + this.canvasView.reset(); + } +}; + + +/** @override */ +HandwritingView.prototype.deactivate = function(rawKeyset) { + this.adapter.unsetInputToolCode(); + goog.dom.classlist.remove(this.getElement().parentElement.parentElement, + Css.HANDWRITING); +}; + + +/** + * Updates the language code. + * + * @param {string} languageCode . + */ +HandwritingView.prototype.setLanguagecode = function(languageCode) { + this.languageCode = languageCode; +}; +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/indicator.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/indicator.js new file mode 100644 index 0000000..a0c8392 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/indicator.js @@ -0,0 +1,99 @@ +// 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.PageIndicator'); + +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('goog.style'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.Element'); + +goog.scope(function() { +var ElementType = i18n.input.chrome.inputview.elements.ElementType; + + + +/** + * The indicator of the current page index. + * + * @param {string} id The id. + * @param {!i18n.input.chrome.inputview.elements.ElementType} type The element + * type. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.Element} + */ +i18n.input.chrome.inputview.elements.content.PageIndicator = function(id, type, + opt_eventTarget) { + goog.base(this, id, type, opt_eventTarget); +}; +goog.inherits(i18n.input.chrome.inputview.elements.content.PageIndicator, + i18n.input.chrome.inputview.elements.Element); +var PageIndicator = i18n.input.chrome.inputview.elements.content.PageIndicator; + + +/** @override */ +PageIndicator.prototype.createDom = function() { + goog.base(this, 'createDom'); + var dom = this.getDomHelper(); + var elem = this.getElement(); + goog.dom.classlist.add(elem, + i18n.input.chrome.inputview.Css.INDICATOR_BACKGROUND); + this.bgElem = goog.dom.createDom(goog.dom.TagName.DIV); + goog.dom.classlist.add(this.bgElem, + i18n.input.chrome.inputview.Css.INDICATOR); + dom.appendChild(elem, this.bgElem); +}; + + +/** @override */ +PageIndicator.prototype.resize = function(width, height) { + this.bgElem.style.height = height + 'px'; + this.getElement().style.width = width + 'px'; + goog.base(this, 'resize', width, height); +}; + + +/** + * Slide the indicator. + * + * @param {number} deltaX The x-coordinate of slide distance. + * @param {number} totalPages The total number of pages. + */ +PageIndicator.prototype.slide = function(deltaX, totalPages) { + var marginLeft = goog.style.getMarginBox(this.bgElem).left + + deltaX / totalPages; + this.bgElem.style.marginLeft = marginLeft + 'px'; +}; + + +/** + * Move the indicator to indicate a page. + * + * @param {number} pageNum The page that needs to be indicated. + * @param {number} totalPages The total number of pages. + */ +PageIndicator.prototype.gotoPage = function(pageNum, totalPages) { + var width = goog.style.getSize(this.getElement()).width; + this.bgElem.style.marginLeft = width / totalPages * pageNum + 'px'; + if (totalPages >= 2) { + this.bgElem.style.width = width / totalPages + 'px'; + } else { + this.bgElem.style.width = 0; + } +}; +}); // goog.scope + + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/keyboardview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/keyboardview.js new file mode 100644 index 0000000..d7033c9 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/keyboardview.js @@ -0,0 +1,208 @@ +// 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.KeyboardView'); + +goog.require('goog.dom.classlist'); +goog.require('goog.object'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.inputview.elements.layout.VerticalLayout'); + + +goog.scope(function() { +var layout = i18n.input.chrome.inputview.elements.layout; +var content = i18n.input.chrome.inputview.elements.content; +var ElementType = i18n.input.chrome.inputview.elements.ElementType; + + + +/** + * The layout view. + * + * @param {string} id The id. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @constructor + * @extends {layout.VerticalLayout} + */ +content.KeyboardView = function(id, opt_eventTarget) { + goog.base(this, id, opt_eventTarget, ElementType.LAYOUT_VIEW); +}; +goog.inherits(content.KeyboardView, layout.VerticalLayout); +var KeyboardView = content.KeyboardView; + + +/** + * The maps of all the soft key view. + * + * @type {!Object.<string, !layout.SoftKeyView>} + * @private + */ +KeyboardView.prototype.softKeyViewMap_; + + +/** + * The soft key map. + * Key: The character belongs to this key. + * + * @type {!Object.<string, !content.SoftKey>} + * @private + */ +KeyboardView.prototype.softKeyMap_; + + +/** + * The mapping between soft key view id and soft key id. + * + * @type {!Object.<string, string>} + * @private + */ +KeyboardView.prototype.mapping_; + + +/** + * The squared factor of the key width * key width. Assumes key1 and key2 + * here, if the squared distance from the center of the key2 to the nearest + * edge of key1 is less than this factor * key1 width * key1 width, we + * take key2 as the nearby key of key1. + * + * @type {number} + * @private + */ +KeyboardView.SQUARED_NEARBY_FACTOR_ = 1.2; + + +/** @override */ +KeyboardView.prototype.createDom = function() { + goog.base(this, 'createDom'); + + var elem = this.getElement(); + goog.dom.classlist.add(elem, i18n.input.chrome.inputview.Css.LAYOUT_VIEW); +}; + + +/** + * Sets up this keyboard view. + * + * @param {!Array.<content.SoftKey>} softKeyList The soft + * key map. + * @param {!Object.<string, layout.SoftKeyView>} softKeyViewMap The soft key + * view map. + * @param {!Object.<string, string>} mapping The mapping from soft key id + * to soft key view id. + */ +KeyboardView.prototype.setUp = function(softKeyList, softKeyViewMap, mapping) { + this.softKeyMap_ = {}; + this.softKeyViewMap_ = softKeyViewMap; + this.mapping_ = mapping; + + for (var i = 0; i < softKeyList.length; i++) { + var sk = softKeyList[i]; + var skv = this.softKeyViewMap_[mapping[sk.id]]; + if (skv) { + skv.bindSoftKey(sk); + } + this.softKeyMap_[sk.id] = sk; + } +}; + + +/** + * Gets the soft key whose id is equal to the code. + * + * @param {string} code The code of the key. + * @return {content.SoftKey} The soft key. + */ +KeyboardView.prototype.getViewForKey = function(code) { + if (code) { + return this.softKeyMap_[code]; + } + return null; +}; + + +/** + * Sets up the nearby keys mapping for each non-Functional key. + * + * @private + */ +KeyboardView.prototype.setUpNearbyKeys_ = function() { + var softKeys = goog.object.getValues(this.softKeyMap_); + for (var i = 0; i < softKeys.length; i++) { + var key1 = softKeys[i]; + if (!this.isQualifiedForSpatial_(key1)) { + continue; + } + for (var j = i + 1; j < softKeys.length; j++) { + var key2 = softKeys[j]; + if (this.isQualifiedForSpatial_(key2) && this.isNearby(key1, key2)) { + // We assume that if key2 is a nearby key for key1, then key1 is + // also a nearby key for key2. + key1.nearbyKeys.push(key2); + key2.nearbyKeys.push(key1); + } + } + } +}; + + +/** + * We only consider character key or compact key to be qualified for spatial + * module. + * + * @param {!content.SoftKey} key . + * @return {boolean} . + * @private + */ +KeyboardView.prototype.isQualifiedForSpatial_ = function(key) { + return key.type == ElementType.CHARACTER_KEY || + key.type == ElementType.COMPACT_KEY; +}; + + +/** + * Checks if key2 is near key1, the algorithm is: + * 1. Finds out the nearest edge point of key1 to the center of key2. + * 2. Calculate the distance from the center of key2 to the nearest edge point. + * 3. If the distance is less than the factor(1.2) * key1 width * key1 width, + * the key2 is nearby key1. + * + * @param {!content.SoftKey} key1 . + * @param {!content.SoftKey} key2 . + * @return {boolean} . + */ +KeyboardView.prototype.isNearby = function(key1, key2) { + var key2Center = key2.centerCoordinate; + var key1Left = key1.topLeftCoordinate.x; + var key1Right = key1Left + key1.width; + var key1Top = key1.topLeftCoordinate.y; + var key1Bottom = key1Top + key1.height; + var edgeX = key2Center.x < key1Left ? key1Left : (key2Center.x > key1Right ? + key1Right : key2Center.x); + var edgeY = key2Center.y < key1Top ? key1Top : (key2Center.y > key1Bottom ? + key1Bottom : key2Center.y); + var dx = key2Center.x - edgeX; + var dy = key2Center.y - edgeY; + return (dx * dx + dy * dy) < KeyboardView. + SQUARED_NEARBY_FACTOR_ * (key1.availableWidth * key1.availableWidth); +}; + + +/** @override */ +KeyboardView.prototype.resize = function(width, height) { + goog.base(this, 'resize', width, height); + + this.setUpNearbyKeys_(); +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/keysetview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/keysetview.js new file mode 100644 index 0000000..61d936d --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/keysetview.js @@ -0,0 +1,727 @@ +// 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.KeysetView'); + +goog.require('goog.array'); +goog.require('goog.dom.classlist'); +goog.require('goog.i18n.bidi'); +goog.require('goog.style'); +goog.require('goog.ui.Container'); +goog.require('i18n.input.chrome.inputview.ConditionName'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.SpecNodeName'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.inputview.elements.content.CandidateButton'); +goog.require('i18n.input.chrome.inputview.elements.content.CanvasView'); +goog.require('i18n.input.chrome.inputview.elements.content.CharacterKey'); +goog.require('i18n.input.chrome.inputview.elements.content.CompactKey'); +goog.require('i18n.input.chrome.inputview.elements.content.EmojiKey'); +goog.require('i18n.input.chrome.inputview.elements.content.EnSwitcherKey'); +goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey'); +goog.require('i18n.input.chrome.inputview.elements.content.KeyboardView'); +goog.require('i18n.input.chrome.inputview.elements.content.MenuKey'); +goog.require('i18n.input.chrome.inputview.elements.content.ModifierKey'); +goog.require('i18n.input.chrome.inputview.elements.content.PageIndicator'); +goog.require('i18n.input.chrome.inputview.elements.content.SpaceKey'); +goog.require('i18n.input.chrome.inputview.elements.content.SwitcherKey'); +goog.require('i18n.input.chrome.inputview.elements.content.TabBarKey'); +goog.require('i18n.input.chrome.inputview.elements.layout.ExtendedLayout'); +goog.require('i18n.input.chrome.inputview.elements.layout.HandwritingLayout'); +goog.require('i18n.input.chrome.inputview.elements.layout.LinearLayout'); +goog.require('i18n.input.chrome.inputview.elements.layout.SoftKeyView'); +goog.require('i18n.input.chrome.inputview.elements.layout.VerticalLayout'); +goog.require('i18n.input.chrome.inputview.util'); + + + +goog.scope(function() { + +var ConditionName = i18n.input.chrome.inputview.ConditionName; +var SpecNodeName = i18n.input.chrome.inputview.SpecNodeName; +var ElementType = i18n.input.chrome.inputview.elements.ElementType; +var content = i18n.input.chrome.inputview.elements.content; +var layout = i18n.input.chrome.inputview.elements.layout; +var Css = i18n.input.chrome.inputview.Css; +var util = i18n.input.chrome.inputview.util; + + + +/** + * The keyboard. + * + * @param {!Object} keyData The data includes soft key definition and key + * mapping. + * @param {!Object} layoutData The layout definition. + * @param {string} keyboardCode The keyboard code. + * @param {string} languageCode The language code. + * @param {!i18n.input.chrome.inputview.Model} model The model. + * @param {string} name The Input Tool name. + * @param {!goog.events.EventTarget=} opt_eventTarget . + * @param {i18n.input.chrome.inputview.Adapter=} opt_adapter . + * @constructor + * @extends {goog.ui.Container} + */ +i18n.input.chrome.inputview.elements.content.KeysetView = function(keyData, + layoutData, keyboardCode, languageCode, model, name, opt_eventTarget, + opt_adapter) { + goog.base(this); + this.setParentEventTarget(opt_eventTarget || null); + + /** + * The key configuration data. + * + * @type {!Object} + * @const + * @private + */ + this.keyData_ = keyData; + + /** + * The layout definition. + * + * @type {!Object} + * @const + * @private + */ + this.layoutData_ = layoutData; + + /** + * The keyboard code. + * + * @type {string} + * @private + */ + this.keyboardCode_ = keyboardCode; + + /** + * The language code. + * + * @protected {string} + */ + this.languageCode = languageCode; + + /** + * The model, the reason use dataModel as its name because model_ will + * conflict with the one in goog.ui.Container. + * + * @type {!i18n.input.chrome.inputview.Model} + * @private + */ + this.dataModel_ = model; + + /** + * The rows in this view, the reason we don't use getChild is that container + * only accepts control as its child, so we have to use + * row.render(this.getElement()) style. + * + * @type {!Array.<layout.LinearLayout>} + * @private + */ + this.rows_ = []; + + /** + * The maps of all the soft key view. + * + * @type {!Object.<string, !layout.SoftKeyView>} + * @private + */ + this.softKeyViewMap_ = {}; + + /** + * The map from the condition to the soft key view. + * + * @type {!Object.<string, !layout.SoftKeyView>} + * @private + */ + this.softKeyConditionMap_ = {}; + + /** + * The on-screen keyboard title. + * + * @type {string} + * @private + */ + this.title_ = name; + + /** + * The bus channel to communicate with background. + * + * @protected {i18n.input.chrome.inputview.Adapter} + */ + this.adapter = opt_adapter || null; + + /** + * The conditions. + * + * @private {!Object.<string, boolean>} + */ + this.conditions_ = {}; + + /** + * whether to display the candidate view or not. + * + * @type {boolean} + */ + this.disableCandidateView = + goog.isDef(this.layoutData_['disableCandidateView']) ? + this.layoutData_['disableCandidateView'] : false; + + /** + * The map of the child views. + * Key: The id of the child element. + * Value: The element. + * + * @private {!Object.<string, !i18n.input.chrome.inputview.elements.Element>} + */ + this.childMap_ = {}; +}; +var KeysetView = i18n.input.chrome.inputview.elements.content.KeysetView; +goog.inherits(KeysetView, goog.ui.Container); + + +/** + * True if the keyset view has shift state. + * + * @type {boolean} + */ +KeysetView.prototype.hasShift = true; + + +/** + * The keyboard. + * + * @type {!content.KeyboardView} + * @private + */ +KeysetView.prototype.keyboardView_; + + +/** + * The keyset code from which jumps to this keyset view. + * + * @type {string} + */ +KeysetView.prototype.fromKeyset = ''; + + +/** + * The handwriting canvas view. + * + * @protected {!content.CanvasView} + */ +KeysetView.prototype.canvasView; + + +/** + * The space key. + * + * @type {!content.SpaceKey} + */ +KeysetView.prototype.spaceKey; + + +/** + * The outer height of the view. + * + * @type {number} + * @private + */ +KeysetView.prototype.outerHeight_ = 0; + + +/** + * The outer width of the view. + * + * @type {number} + * @private + */ +KeysetView.prototype.outerWidth_ = 0; + + +/** @override */ +KeysetView.prototype.createDom = function() { + goog.base(this, 'createDom'); + + this.hasShift = !this.keyData_[SpecNodeName.NO_SHIFT]; + var elem = this.getElement(); + elem.id = this.keyboardCode_.replace(/\./g, '-'); + goog.dom.classlist.add(elem, i18n.input.chrome.inputview.Css.VIEW); + + var children = this.layoutData_['children']; + for (var i = 0; i < children.length; i++) { + var child = children[i]; + var layoutElem = /** @type {!layout.LinearLayout} */ + (this.createLayoutElement_(child[i18n.input.chrome.inputview. + SpecNodeName.SPEC], this)); + // Can't use addChild here, because container only allow control as its + // child. + if (layoutElem) { + layoutElem.render(elem); + this.rows_.push(layoutElem); + } + } + + var softKeyList = []; + var keySpecs = this.keyData_[SpecNodeName.KEY_LIST]; + var hasAltGrCharacterInTheKeyset = this.hasAltGrCharacterInTheKeyset_( + keySpecs); + for (var i = 0; i < keySpecs.length; i++) { + var softKey = this.createKey_(keySpecs[i][SpecNodeName.SPEC], + hasAltGrCharacterInTheKeyset); + if (softKey) { + softKeyList.push(softKey); + } + } + var mapping = this.keyData_[ + SpecNodeName.MAPPING]; + this.keyboardView_.setUp(softKeyList, this.softKeyViewMap_, mapping); +}; + + +/** + * Updates the view. + */ +KeysetView.prototype.update = function() { + this.keyboardView_.update(); +}; + + +/** + * Resizes the view. + * + * @param {number} outerWidth The width of the outer space. + * @param {number} outerHeight The height of the outer space. + * @param {boolean=} opt_force Forces to resize the view. + */ +KeysetView.prototype.resize = function(outerWidth, outerHeight, opt_force) { + var needResize = !!opt_force || (this.outerHeight_ != outerHeight || + this.outerWidth_ != outerWidth); + if (this.getElement() && needResize) { + this.outerHeight_ = outerHeight; + this.outerWidth_ = outerWidth; + var elem = this.getElement(); + goog.style.setSize(elem, outerWidth, outerHeight); + + var weightArray = []; + for (var i = 0; i < this.rows_.length; i++) { + var row = this.rows_[i]; + weightArray.push(row.getHeightInWeight()); + } + + var splitedHeight = i18n.input.chrome.inputview.util.splitValue(weightArray, + outerHeight); + for (var i = 0; i < this.rows_.length; i++) { + var row = this.rows_[i]; + row.resize(outerWidth, splitedHeight[i]); + } + } +}; + + +/** + * Gets the total height in weight. + * + * @return {number} The total height in weight. + */ +KeysetView.prototype.getHeightInWeight = function() { + var heightInWeight = 0; + for (var i = 0; i < this.rows_.length; i++) { + var row = this.rows_[i]; + heightInWeight += row.getHeightInWeight(); + } + return heightInWeight; +}; + + +/** + * Apply conditions. + * + * @param {!Object.<string, boolean>} conditions The conditions. + */ +KeysetView.prototype.applyConditions = function(conditions) { + this.conditions_ = conditions; + for (var condition in conditions) { + var softKeyView = this.softKeyConditionMap_[condition]; + var isConditionEnabled = conditions[condition]; + if (softKeyView) { + softKeyView.setVisible(isConditionEnabled); + var softKeyViewGetWeight = this.softKeyViewMap_[softKeyView. + giveWeightTo]; + if (softKeyViewGetWeight) { + // Only supports horizontal weight transfer now. + softKeyViewGetWeight.dynamicaGrantedWeight += isConditionEnabled ? + 0 : softKeyView.widthInWeight; + } + } + } + + // Adjusts the width of globe key and menu key according to the mock when they + // both show up. + // TODO: This is hacky. Remove the hack once figure out a better way. + var showGlobeKey = conditions[ConditionName.SHOW_GLOBE_OR_SYMBOL]; + var showMenuKey = conditions[ConditionName.SHOW_MENU]; + var menuKeyView = this.softKeyConditionMap_[ConditionName.SHOW_MENU]; + var globeKeyView = + this.softKeyConditionMap_[ConditionName.SHOW_GLOBE_OR_SYMBOL]; + if (menuKeyView && globeKeyView) { + var softKeyViewGetWeight = + this.softKeyViewMap_[menuKeyView.giveWeightTo]; + if (softKeyViewGetWeight) { + if (showGlobeKey && showMenuKey) { + globeKeyView.dynamicaGrantedWeight = -0.1; + menuKeyView.dynamicaGrantedWeight = -0.4; + softKeyViewGetWeight.dynamicaGrantedWeight += 0.5; + } + } + } +}; + + +/** + * Updates the condition. + * + * @param {string} name . + * @param {boolean} value . + */ +KeysetView.prototype.updateCondition = function(name, value) { + for (var id in this.softKeyViewMap_) { + var skv = this.softKeyViewMap_[id]; + skv.dynamicaGrantedWeight = 0; + } + this.conditions_[name] = value; + this.applyConditions(this.conditions_); + this.resize(this.outerWidth_, this.outerHeight_, true); + this.update(); +}; + + +/** + * Creates the element according to its type. + * + * @param {!Object} spec The specification. + * @param {!goog.events.EventTarget=} opt_eventTarget The event target. + * @return {i18n.input.chrome.inputview.elements.Element} The element. + * @private + */ +KeysetView.prototype.createElement_ = function(spec, opt_eventTarget) { + var type = spec[SpecNodeName.TYPE]; + var id = spec[SpecNodeName.ID]; + var widthInWeight = spec[ + SpecNodeName.WIDTH_IN_WEIGHT]; + var heightInWeight = spec[ + SpecNodeName.HEIGHT_IN_WEIGHT]; + var width = spec[SpecNodeName.WIDTH]; + var height = spec[SpecNodeName.HEIGHT]; + var padding = spec[SpecNodeName.PADDING]; + var widthPercent = spec[SpecNodeName.WIDTH_PERCENT]; + var heightPercent = spec[SpecNodeName.HEIGHT_PERCENT]; + var elem = null; + switch (type) { + case ElementType.SOFT_KEY_VIEW: + var condition = spec[SpecNodeName.CONDITION]; + var giveWeightTo = spec[SpecNodeName.GIVE_WEIGHT_TO]; + elem = new layout.SoftKeyView(id, widthInWeight, + heightInWeight, condition, giveWeightTo, opt_eventTarget); + this.softKeyConditionMap_[condition] = elem; + break; + case ElementType.LINEAR_LAYOUT: + var opt_iconCssClass = spec[SpecNodeName.ICON_CSS_CLASS]; + elem = new layout.LinearLayout(id, opt_eventTarget, opt_iconCssClass); + break; + case ElementType.EXTENDED_LAYOUT: + elem = new layout.ExtendedLayout(id, opt_eventTarget); + break; + case ElementType.VERTICAL_LAYOUT: + elem = new layout.VerticalLayout(id, opt_eventTarget); + break; + case ElementType.LAYOUT_VIEW: + this.keyboardView_ = new content.KeyboardView(id, opt_eventTarget); + elem = this.keyboardView_; + break; + case ElementType.CANVAS_VIEW: + this.canvasView = new content.CanvasView(id, widthInWeight, + heightInWeight, opt_eventTarget, this.adapter); + elem = this.canvasView; + break; + case ElementType.HANDWRITING_LAYOUT: + elem = new layout.HandwritingLayout(id, opt_eventTarget); + break; + } + if (elem) { + this.childMap_[id] = elem; + } + return elem; +}; + + +/** + * Creates the layout element. + * + * @param {!Object} spec The specification for the element. + * @param {!goog.events.EventTarget=} opt_parentEventTarget The parent event + * target. + * @return {i18n.input.chrome.inputview.elements.Element} The element. + * @private + */ +KeysetView.prototype.createLayoutElement_ = function(spec, + opt_parentEventTarget) { + var element = this.createElement_(spec, opt_parentEventTarget); + if (!element) { + return null; + } + + var children = spec[SpecNodeName.CHILDREN]; + if (children) { + children = goog.array.flatten(children); + for (var i = 0; i < children.length; i++) { + var child = children[i]; + var childElem = this.createLayoutElement_( + child[SpecNodeName.SPEC], element); + if (childElem) { + element.addChild(childElem, true); + } + } + } + if (element.type == ElementType.SOFT_KEY_VIEW) { + this.softKeyViewMap_[element.id] = + /** @type {!layout.SoftKeyView} */ (element); + } + return element; +}; + + +/** + * Checks if there is altgr character. + * + * @param {!Array.<!Object>} keySpecs The list of key specs. + * @return {[boolean, boolean]} A list with two boolean values, the first is + * for whether there is altgr character of letter keys, the second is for + * symbol keys. + * @private + */ +KeysetView.prototype.hasAltGrCharacterInTheKeyset_ = function(keySpecs) { + var result = [false, false]; + for (var i = 0; i < keySpecs.length; i++) { + var spec = keySpecs[i]; + var characters = spec[SpecNodeName.CHARACTERS]; + if (characters && (!!characters[2] || !!characters[3])) { + var index = i18n.input.chrome.inputview.util.isLetterKey( + characters) ? 0 : 1; + result[index] = true; + } + } + return result; +}; + + +/** + * Creates a soft key. + * + * @param {Object} spec The specification. + * @param {!Array.<boolean, boolean>} hasAltGrCharacterInTheKeyset The list + * of results for whether there is altgr character, the first for letter + * key, the second for symbol key. + * @return {i18n.input.chrome.inputview.elements.Element} The soft key. + * @private + */ +KeysetView.prototype.createKey_ = function(spec, hasAltGrCharacterInTheKeyset) { + var type = spec[SpecNodeName.TYPE]; + var id = spec[SpecNodeName.ID]; + var keyCode = spec[SpecNodeName.KEY_CODE]; // Could be undefined. + var name = spec[SpecNodeName.NAME]; + var characters = spec[SpecNodeName.CHARACTERS]; + var iconCssClass = spec[SpecNodeName.ICON_CSS_CLASS]; + var textCssClass = spec[SpecNodeName.TEXT_CSS_CLASS]; + var toKeyset = spec[SpecNodeName.TO_KEYSET]; + var toKeysetName = spec[SpecNodeName. + TO_KEYSET_NAME]; + var elem = null; + switch (type) { + case ElementType.MODIFIER_KEY: + var toState = spec[SpecNodeName.TO_STATE]; + var supportSticky = spec[SpecNodeName.SUPPORT_STICKY]; + elem = new content.ModifierKey(id, name, iconCssClass, toState, + this.dataModel_.stateManager, supportSticky); + break; + case ElementType.SPACE_KEY: + this.spaceKey = new content.SpaceKey(id, this.dataModel_.stateManager, + this.title_, characters, undefined, iconCssClass); + elem = this.spaceKey; + break; + case ElementType.EN_SWITCHER: + elem = new content.EnSwitcherKey(id, type, name, iconCssClass, + this.dataModel_.stateManager, Css.EN_SWITCHER_DEFAULT, + Css.EN_SWITCHER_ENGLISH); + break; + case ElementType.BACKSPACE_KEY: + case ElementType.ENTER_KEY: + case ElementType.TAB_KEY: + case ElementType.ARROW_UP: + case ElementType.ARROW_DOWN: + case ElementType.ARROW_LEFT: + case ElementType.ARROW_RIGHT: + case ElementType.HIDE_KEYBOARD_KEY: + case ElementType.GLOBE_KEY: + elem = new content.FunctionalKey(id, type, name, iconCssClass); + break; + case ElementType.TAB_BAR_KEY: + elem = new content.TabBarKey(id, type, name, iconCssClass, + toKeyset, this.dataModel_.stateManager); + break; + case ElementType.EMOJI_KEY: + var text = spec[SpecNodeName.TEXT]; + var isEmoticon = spec[SpecNodeName.IS_EMOTICON]; + elem = new content.EmojiKey(id, type, text, iconCssClass, isEmoticon); + break; + case ElementType.PAGE_INDICATOR: + elem = new content.PageIndicator(id, type); + break; + case ElementType.IME_SWITCH: + elem = new content.FunctionalKey(id, type, name, iconCssClass, undefined, + textCssClass); + break; + case ElementType.MENU_KEY: + elem = new content.MenuKey(id, type, name, iconCssClass, toKeyset); + break; + case ElementType.SWITCHER_KEY: + var record = spec[SpecNodeName.RECORD]; + elem = new content.SwitcherKey(id, type, name, iconCssClass, toKeyset, + toKeysetName, record); + break; + case ElementType.COMPACT_KEY: + var hintText = spec[SpecNodeName.HINT_TEXT]; + var text = spec[SpecNodeName.TEXT]; + var marginLeftPercent = spec[SpecNodeName.MARGIN_LEFT_PERCENT]; + var marginRightPercent = spec[SpecNodeName.MARGIN_RIGHT_PERCENT]; + var isGrey = spec[SpecNodeName.IS_GREY]; + var moreKeys = spec[SpecNodeName.MORE_KEYS]; + elem = new content.CompactKey( + id, text, hintText, this.dataModel_.stateManager, this.hasShift, + marginLeftPercent, marginRightPercent, isGrey, moreKeys); + break; + case ElementType.CHARACTER_KEY: + var isLetterKey = i18n.input.chrome.inputview.util.isLetterKey( + characters); + elem = new content.CharacterKey(id, keyCode || 0, + characters, isLetterKey, hasAltGrCharacterInTheKeyset[isLetterKey], + this.dataModel_.settings.alwaysRenderAltGrCharacter, + this.dataModel_.stateManager, + goog.i18n.bidi.isRtlLanguage(this.languageCode)); + break; + + case ElementType.BACK_BUTTON: + elem = new content.CandidateButton( + id, ElementType.BACK_BUTTON, iconCssClass, + chrome.i18n.getMessage('HANDWRITING_BACK'), this); + break; + } + if (elem) { + this.childMap_[id] = elem; + } + return elem; +}; + + +/** + * Gets the view for the key. + * + * @param {string} code The code of the key. + * @return {i18n.input.chrome.inputview.elements.content.SoftKey} The soft key. + */ +KeysetView.prototype.getViewForKey = function(code) { + return this.keyboardView_.getViewForKey(code); +}; + + +/** + * Gets the width in weight for a entire row. + * + * @return {number} . + */ +KeysetView.prototype.getWidthInWeight = function() { + if (this.rows_.length > 0) { + return this.rows_[0].getWidthInWeight(); + } + + return 0; +}; + + +/** + * Whether there are strokes on canvas. + * + * @return {boolean} Whether there are strokes on canvas. + */ +KeysetView.prototype.hasStrokesOnCanvas = function() { + if (this.canvasView) { + return this.canvasView.hasStrokesOnCanvas(); + } else { + return false; + } +}; + + +/** + * Cleans the stokes. + */ +KeysetView.prototype.cleanStroke = function() { + if (this.canvasView) { + this.canvasView.reset(); + } +}; + + +/** + * Checks the view whether is handwriting panel. + * + * @return {boolean} Whether is handwriting panel. + */ +KeysetView.prototype.isHandwriting = function() { + return this.keyboardCode_ == 'hwt'; +}; + + +/** + * Get the subview of the keysetview according to the id. + * + * @param {string} id The id. + * @return {i18n.input.chrome.inputview.elements.Element} + */ +KeysetView.prototype.getChildViewById = function(id) { + return this.childMap_[id]; +}; + + +/** + * Activate the current keyset view instance. + * + * @param {string} rawKeyset The raw keyset. + */ +KeysetView.prototype.activate = function(rawKeyset) { + if (goog.array.contains(util.KEYSETS_HAVE_EN_SWTICHER, rawKeyset)) { + this.updateCondition(ConditionName.SHOW_EN_SWITCHER_KEY, true); + var elem = this.getElement(); + var name = rawKeyset.replace(/\-.*$/, '').toUpperCase(); + goog.dom.classlist.add(elem, Css[name]); + } +}; + + +/** + * Deactivate the current keyset view instance. + * + * @param {string} rawKeyset The raw keyset id map to the instance keyset id. + */ +KeysetView.prototype.deactivate = goog.nullFunction; +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/menuitem.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/menuitem.js new file mode 100644 index 0000000..f4e884a --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/menuitem.js @@ -0,0 +1,138 @@ +// 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.MenuItem'); + +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('goog.style'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.Element'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); + + +goog.scope(function() { +var Css = i18n.input.chrome.inputview.Css; +var TagName = goog.dom.TagName; +var ElementType = i18n.input.chrome.inputview.elements.ElementType; + +/** + * The menu item in the menu. + * + * @param {string} id . + * @param {Object} item The list time to be added. + * @param {i18n.input.chrome.inputview.elements.content.MenuItem.Type} + * menuItemType . + * @param {goog.events.EventTarget=} opt_eventTarget The parent event target. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.Element} + */ +i18n.input.chrome.inputview.elements.content.MenuItem = function(id, item, + menuItemType, opt_eventTarget) { + goog.base(this, id, ElementType.MENU_ITEM, opt_eventTarget); + + /** + * @type {Object} + * @private + * */ + this.item_ = item; + + /** + * @type {i18n.input.chrome.inputview.elements.content.MenuItem.Type} + * @private + */ + this.menuItemType_ = menuItemType; + + this.pointerConfig.stopEventPropagation = false; + this.pointerConfig.preventDefault = false; +}; +var MenuItem = i18n.input.chrome.inputview.elements.content.MenuItem; +goog.inherits(MenuItem, i18n.input.chrome.inputview.elements.Element); + + +/** + * The type of this MenuItem. + * + * @enum {number} + */ +MenuItem.Type = { + LIST_ITEM: 0, + FOOTER_ITEM: 1 +}; + + +/** @override */ +MenuItem.prototype.createDom = function() { + goog.base(this, 'createDom'); + + var dom = this.getDomHelper(); + var elem = this.getElement(); + switch (this.menuItemType_) { + case MenuItem.Type.LIST_ITEM: + goog.dom.classlist.add(elem, + i18n.input.chrome.inputview.Css.MENU_LIST_ITEM); + var indicatorDiv = dom.createDom(goog.dom.TagName.DIV, + i18n.input.chrome.inputview.Css.MENU_LIST_INDICATOR); + if (this.item_['iconURL']) { + indicatorDiv.style.backgroundImage = + 'url(' + this.item_['iconURL'] + ')'; + } else { + var indicatorTextDiv = dom.createDom(goog.dom.TagName.DIV, + i18n.input.chrome.inputview.Css.MENU_LIST_INDICATOR_NAME); + indicatorTextDiv.textContent = this.item_['indicator']; + dom.appendChild(indicatorDiv, indicatorTextDiv); + } + dom.appendChild(elem, indicatorDiv); + + var nameDiv = dom.createDom(goog.dom.TagName.DIV, + i18n.input.chrome.inputview.Css.MENU_LIST_NAME); + var nameText = dom.createDom(goog.dom.TagName.DIV); + nameText.innerText = this.item_['name']; + dom.appendChild(nameDiv, nameText); + dom.appendChild(elem, nameDiv); + break; + case MenuItem.Type.FOOTER_ITEM: + goog.dom.classlist.add(elem, + i18n.input.chrome.inputview.Css.MENU_FOOTER_ITEM); + goog.dom.classlist.add(elem, this.item_['iconCssClass']); + } +}; + + +/** + * Gets the command of this menu item. + */ +MenuItem.prototype.getCommand = function() { + return this.item_['command']; +}; + + +/** @override */ +MenuItem.prototype.setHighlighted = function(highlight) { + if (highlight) { + goog.dom.classlist.add(this.getElement(), Css.ELEMENT_HIGHLIGHT); + } else { + goog.dom.classlist.remove(this.getElement(), Css.ELEMENT_HIGHLIGHT); + } +}; + + +/** + * Append a checkmark to this element. + */ +MenuItem.prototype.check = function() { + goog.dom.classlist.add(this.getElement(), Css.CHECKED_MENU_LIST); +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/menukey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/menukey.js new file mode 100644 index 0000000..62fc950 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/menukey.js @@ -0,0 +1,63 @@ +// 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.MenuKey'); + +goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey'); + +goog.scope(function() { + + + +/** + * The three dots menu key. + * + * @param {string} id The id. + * @param {!i18n.input.chrome.inputview.elements.ElementType} type The element + * type. + * @param {string} text The text. + * @param {string} iconCssClass The css class for the icon. + * @param {string} toKeyset The keyset id. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.content.FunctionalKey} + */ +i18n.input.chrome.inputview.elements.content.MenuKey = function(id, type, + text, iconCssClass, toKeyset, opt_eventTarget) { + goog.base(this, id, type, text, iconCssClass, opt_eventTarget); + + /** + * The id of the key set to go after this switcher key in menu is pressed. + * + * @type {string} + */ + this.toKeyset = toKeyset; + +}; +goog.inherits(i18n.input.chrome.inputview.elements.content.MenuKey, + i18n.input.chrome.inputview.elements.content.FunctionalKey); +var MenuKey = i18n.input.chrome.inputview.elements.content.MenuKey; + + +/** @override */ +MenuKey.prototype.resize = function(width, + height) { + goog.base(this, 'resize', width, height); + + // TODO: override width to remove space between menu key and the + // following key. +}; + + + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/menuview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/menuview.js new file mode 100644 index 0000000..613df77 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/menuview.js @@ -0,0 +1,372 @@ +// 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.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('goog.style'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.Element'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.inputview.elements.content.MenuItem'); +goog.require('i18n.input.chrome.inputview.util'); + + + +goog.scope(function() { +var ElementType = i18n.input.chrome.inputview.elements.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.. + * + * @param {goog.events.EventTarget=} opt_eventTarget The parent event target. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.Element} + */ +// TODO: MenuView could extend from AltDataView after some refactor. +i18n.input.chrome.inputview.elements.content.MenuView = function( + opt_eventTarget) { + goog.base(this, '', ElementType.MENU_VIEW, opt_eventTarget); + + 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 supported command. + * + * @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 limiation is because menu view can not be + * higher than the keyboard view. + * + * @type {number} + * @private + */ +MenuView.MAXIMAL_VISIBLE_IMES_ = 3; + + +/** + * The width of the popup menu. + * + * @type {number} + * @private + */ +MenuView.WIDTH_ = 300; + + +/** + * The height of the popup menu item. + * + * @type {number} + * @private + */ +MenuView.LIST_ITEM_HEIGHT_ = 50; + + +/** + * 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. + * + * @type {!Element} + * @private + */ +MenuView.prototype.coverElement_; + + +/** @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); + this.coverElement_ = dom.createDom(goog.dom.TagName.DIV, Css.ALTDATA_COVER); + dom.appendChild(document.body, this.coverElement_); + goog.style.setElementShown(this.coverElement_, false); + + this.coverElement_['view'] = this; +}; + + +/** @override */ +MenuView.prototype.enterDocument = function() { + goog.base(this, 'enterDocument'); + + goog.style.setElementShown(this.getElement(), false); +}; + + +/** + * Shows the menu view. + * @param {!i18n.input.chrome.inputview.elements.content.SoftKey} key The key + * triggerred this altdata 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>} 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. + */ +MenuView.prototype.show = function(key, currentKeysetId, isCompact, + enableCompactLayout, currentInputMethod, inputMethods, hasHwt, + enableSettings, hasEmoji) { + var ElementType = i18n.input.chrome.inputview.elements.ElementType; + var dom = this.getDomHelper(); + if (key.type != ElementType.MENU_KEY) { + console.error('Only menu key should trigger menu view to show'); + return; + } + this.triggeredBy = key; + var coordinate = goog.style.getClientPosition(key.getElement()); + var x = coordinate.x; + // y is the maximual height that menu view can have. + var y = coordinate.y; + + goog.style.setElementShown(this.getElement(), true); + // may not need to remove child. + dom.removeChildren(this.getElement()); + + var totalHeight = 0; + totalHeight += this.addInputMethodItems_(currentInputMethod, inputMethods); + totalHeight += this.addLayoutSwitcherItem_(key, currentKeysetId, isCompact, + enableCompactLayout); + if (hasHwt || enableSettings || hasEmoji) { + totalHeight += this.addFooterItems_(hasHwt, enableSettings, hasEmoji); + } + + + var left = x; + //TODO: 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>} inputMethods The list of activated input methods. + * @return {number} The height of the ime list container. + * @private + */ +MenuView.prototype.addInputMethodItems_ = function(currentInputMethod, + inputMethods) { + var dom = this.getDomHelper(); + var container = dom.createDom(goog.dom.TagName.DIV, + Css.IME_LIST_CONTAINER); + + for (var i = 0; i < inputMethods.length; i++) { + var inputMethod = inputMethods[i]; + var listItem = {}; + listItem['indicator'] = inputMethod['indicator']; + listItem['name'] = inputMethod['name']; + listItem['command'] = + [MenuView.Command.SWITCH_IME, inputMethod['id']]; + var imeItem = new MenuItem(String(i), listItem, MenuItem.Type.LIST_ITEM); + imeItem.render(container); + if (currentInputMethod == inputMethod['id']) { + imeItem.check(); + } + goog.style.setSize(imeItem.getElement(), MenuView.WIDTH_, + MenuView.LIST_ITEM_HEIGHT_); + } + + var containerHeight = inputMethods.length > MenuView.MAXIMAL_VISIBLE_IMES_ ? + MenuView.LIST_ITEM_HEIGHT_ * MenuView.MAXIMAL_VISIBLE_IMES_ : + MenuView.LIST_ITEM_HEIGHT_ * inputMethods.length; + goog.style.setSize(container, MenuView.WIDTH_, 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 altdata 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; + } + var dom = this.getDomHelper(); + // Adds layout switcher. + var layoutSwitcher = {}; + if (isCompact) { + layoutSwitcher['iconURL'] = 'images/regular_size.png'; + layoutSwitcher['name'] = chrome.i18n.getMessage('SWITCH_TO_FULL_LAYOUT'); + var fullLayoutId = currentKeysetId.split('.')[0]; + layoutSwitcher['command'] = + [MenuView.Command.SWITCH_KEYSET, fullLayoutId]; + } else { + layoutSwitcher['iconURL'] = 'images/compact.png'; + layoutSwitcher['name'] = chrome.i18n.getMessage('SWITCH_TO_COMPACT_LAYOUT'); + if (goog.array.contains(i18n.input.chrome.inputview.util.KEYSETS_USE_US, + currentKeysetId)) { + key.toKeyset = currentKeysetId + '.compact.qwerty'; + } + layoutSwitcher['command'] = + [MenuView.Command.SWITCH_KEYSET, key.toKeyset]; + + } + var layoutSwitcherItem = new MenuItem('MenuLayoutSwitcher', layoutSwitcher, + MenuItem.Type.LIST_ITEM); + layoutSwitcherItem.render(this.getElement()); + goog.style.setSize(layoutSwitcherItem.getElement(), MenuView.WIDTH_, + MenuView.LIST_ITEM_HEIGHT_); + + return MenuView.LIST_ITEM_HEIGHT_; +}; + + +/** + * 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 = {}; + emoji['iconCssClass'] = Css.MENU_FOOTER_EMOJI_BUTTON; + emoji['command'] = [MenuView.Command.OPEN_EMOJI]; + var emojiFooter = new MenuItem('emoji', emoji, + MenuItem.Type.FOOTER_ITEM); + emojiFooter.render(footer); + } + + if (hasHwt) { + var handWriting = {}; + handWriting['iconCssClass'] = Css.MENU_FOOTER_HANDWRITING_BUTTON; + handWriting['command'] = [MenuView.Command.OPEN_HANDWRITING]; + var handWritingFooter = new MenuItem('handwriting', handWriting, + MenuItem.Type.FOOTER_ITEM); + handWritingFooter.render(footer); + } + + if (enableSettings) { + var setting = {}; + setting['iconCssClass'] = Css.MENU_FOOTER_SETTING_BUTTON; + setting['command'] = [MenuView.Command.OPEN_SETTING]; + var settingFooter = new MenuItem('setting', setting, + MenuItem.Type.FOOTER_ITEM); + settingFooter.render(footer); + } + + // Sets footer itmes' width. + var elems = dom.getChildren(footer); + var len = elems.length; + var subWidth = Math.ceil(MenuView.WIDTH_ / len); + var i = 0; + for (; i < len - 1; i++) { + elems[i].style.width = subWidth + 'px'; + } + elems[i].style.width = (MenuView.WIDTH_ - subWidth * (len - 1)) + 'px'; + + dom.appendChild(this.getElement(), footer); + goog.style.setSize(footer, MenuView.WIDTH_, MenuView.LIST_ITEM_HEIGHT_); + + return MenuView.LIST_ITEM_HEIGHT_; +}; + + +/** + * Gets the cover element. + * + * @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.getElement()['view'] = null; + + goog.base(this, 'disposeInternal'); +}; +}); // goog.scope + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/modifierkey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/modifierkey.js new file mode 100644 index 0000000..7a973ae --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/modifierkey.js @@ -0,0 +1,146 @@ +// 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.ModifierKey'); + +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.StateType'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey'); + + + +goog.scope(function() { +var StateType = i18n.input.chrome.inputview.StateType; + + + +/** + * The modifier key. + * + * @param {string} id the id. + * @param {string} name the name. + * @param {string} iconCssClass The css class for the icon. + * @param {!i18n.input.chrome.inputview.StateType} toState The state. + * @param {!i18n.input.chrome.inputview.StateManager} stateManager The state + * manager. + * @param {boolean} supportSticky True if this modifier key supports sticky. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.content.FunctionalKey} + */ +i18n.input.chrome.inputview.elements.content.ModifierKey = function(id, name, + iconCssClass, toState, stateManager, supportSticky, opt_eventTarget) { + goog.base(this, id, + i18n.input.chrome.inputview.elements.ElementType.MODIFIER_KEY, + name, iconCssClass, opt_eventTarget); + + /** + * The state when click on the key. + * + * @type {!i18n.input.chrome.inputview.StateType} + */ + this.toState = toState; + + /** + * The state manager. + * + * @type {!i18n.input.chrome.inputview.StateManager} + * @private + */ + this.stateManager_ = stateManager; + + /** + * True if supports sticky. + * + * @type {boolean} + */ + this.supportSticky = supportSticky; + + if (supportSticky) { + this.pointerConfig.dblClick = true; + this.pointerConfig.longPressWithPointerUp = true; + this.pointerConfig.longPressDelay = 1200; + } +}; +goog.inherits(i18n.input.chrome.inputview.elements.content.ModifierKey, + i18n.input.chrome.inputview.elements.content.FunctionalKey); +var ModifierKey = i18n.input.chrome.inputview.elements.content.ModifierKey; + + +/** + * The dot icon for capslock. + * + * @type {!Element} + * @private + */ +ModifierKey.prototype.dotIcon_; + + +/** + * True if a double click is just happened. + * + * @type {boolean} + */ +ModifierKey.prototype.isDoubleClicking = false; + + +/** @override */ +ModifierKey.prototype.createDom = function() { + goog.base(this, 'createDom'); + + if (this.toState == i18n.input.chrome.inputview.StateType.CAPSLOCK || + this.supportSticky) { + var dom = this.getDomHelper(); + this.dotIcon_ = dom.createDom(goog.dom.TagName.DIV, + i18n.input.chrome.inputview.Css.CAPSLOCK_DOT); + dom.appendChild(this.tableCell, this.dotIcon_); + } + + this.setAriaLabel(this.getChromeVoxMessage()); +}; + + +/** @override */ +ModifierKey.prototype.update = function() { + var isStateEnabled = this.stateManager_.hasState(this.toState); + var isSticky = this.stateManager_.isSticky(this.toState); + this.setHighlighted(isStateEnabled); + if (this.dotIcon_) { + if (isStateEnabled && isSticky) { + goog.dom.classlist.add(this.dotIcon_, + i18n.input.chrome.inputview.Css.CAPSLOCK_DOT_HIGHLIGHT); + } else { + goog.dom.classlist.remove(this.dotIcon_, + i18n.input.chrome.inputview.Css.CAPSLOCK_DOT_HIGHLIGHT); + } + } +}; + + +/** @override */ +ModifierKey.prototype.getChromeVoxMessage = function() { + switch (this.toState) { + case StateType.SHIFT: + return chrome.i18n.getMessage('SHIFT'); + case StateType.CAPSLOCK: + return chrome.i18n.getMessage('CAPSLOCK'); + case StateType.ALTGR: + return chrome.i18n.getMessage('ALTGR'); + } + return ''; +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/morekeysshiftoperation.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/morekeysshiftoperation.js new file mode 100644 index 0000000..1b9a2da --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/morekeysshiftoperation.js @@ -0,0 +1,27 @@ +// 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.MoreKeysShiftOperation'); + + +/** + * The operations on more keys when the shift is down. + * + * @enum {number} + */ +i18n.input.chrome.inputview.MoreKeysShiftOperation = { + STABLE: 0, + TO_UPPER_CASE: 1, + TO_LOWER_CASE: 2 +}; + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/softkey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/softkey.js new file mode 100644 index 0000000..8debbc4 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/softkey.js @@ -0,0 +1,127 @@ +// 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.SoftKey'); + +goog.require('goog.dom.classlist'); +goog.require('goog.math.Coordinate'); +goog.require('goog.style'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.Element'); +goog.require('i18n.input.chrome.inputview.util'); + + + +goog.scope(function() { + + + +/** + * The base soft key class. + * + * @param {string} id The id. + * @param {!i18n.input.chrome.inputview.elements.ElementType} type The element + * type. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.Element} + */ +i18n.input.chrome.inputview.elements.content.SoftKey = function(id, type, + opt_eventTarget) { + goog.base(this, id, type, opt_eventTarget); + + /** + * The available width. + * + * @type {number} + */ + this.availableWidth = 0; + + /** + * The available height. + * + * @type {number} + */ + this.availableHeight = 0; + + /** + * The nearby keys. + * + * @type {!Array.<!SoftKey>} + */ + this.nearbyKeys = []; +}; +goog.inherits(i18n.input.chrome.inputview.elements.content.SoftKey, + i18n.input.chrome.inputview.elements.Element); +var SoftKey = i18n.input.chrome.inputview.elements.content.SoftKey; + + +/** + * The coordinate of the center point. + * + * @type {!goog.math.Coordinate} + */ +SoftKey.prototype.centerCoordinate; + + +/** + * The coordinate of the top-left point. + * + * @type {!goog.math.Coordinate} + */ +SoftKey.prototype.topLeftCoordinate; + + +/** + * The gaussian estimator. + * + * @type {!i18n.input.chrome.inputview.elements.content.GaussianEstimator} + */ +SoftKey.prototype.estimator; + + +/** @override */ +SoftKey.prototype.createDom = function() { + goog.base(this, 'createDom'); + + goog.dom.classlist.add(this.getElement(), + i18n.input.chrome.inputview.Css.SOFT_KEY); +}; + + +/** @override */ +SoftKey.prototype.resize = function(width, + height) { + goog.base(this, 'resize', width, height); + + var elem = this.getElement(); + var borderWidth = i18n.input.chrome.inputview.util.getPropertyValue( + elem, 'borderWidth'); + var marginTop = i18n.input.chrome.inputview.util.getPropertyValue( + elem, 'marginTop'); + var marginBottom = i18n.input.chrome.inputview.util.getPropertyValue( + elem, 'marginBottom'); + var marginLeft = i18n.input.chrome.inputview.util.getPropertyValue( + elem, 'marginLeft'); + var marginRight = i18n.input.chrome.inputview.util.getPropertyValue( + elem, 'marginRight'); + var w = width - borderWidth * 2 - marginLeft - marginRight; + var h = height - borderWidth * 2 - marginTop - marginBottom; + elem.style.width = w + 'px'; + elem.style.height = h + 'px'; + + this.availableWidth = w; + this.availableHeight = h; +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/spacekey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/spacekey.js new file mode 100644 index 0000000..38a8cc4 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/spacekey.js @@ -0,0 +1,120 @@ +// 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.SpaceKey'); + +goog.require('goog.dom'); +goog.require('goog.dom.classlist'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.StateType'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey'); + + + +goog.scope(function() { + + + +/** + * The space key. + * + * @param {string} id the id. + * @param {!i18n.input.chrome.inputview.StateManager} stateManager The state + * manager. + * @param {string} title The keyboard title. + * @param {!Array.<string>=} opt_characters The characters. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @param {string=} opt_iconCss The icon CSS class + * @constructor + * @extends {i18n.input.chrome.inputview.elements.content.FunctionalKey} + */ +i18n.input.chrome.inputview.elements.content.SpaceKey = function(id, + stateManager, title, opt_characters, opt_eventTarget, opt_iconCss) { + goog.base(this, id, i18n.input.chrome.inputview.elements.ElementType. + SPACE_KEY, opt_iconCss ? '' : title, opt_iconCss || '', opt_eventTarget); + + /** + * The characters. + * + * @type {!Array.<string>} + * @private + */ + this.characters_ = opt_characters || []; + + /** + * The state manager. + * + * @type {!i18n.input.chrome.inputview.StateManager} + * @private + */ + this.stateManager_ = stateManager; + + // Double click on space key may convert two spaces to a period followed by a + // space. + this.pointerConfig.dblClick = true; + this.pointerConfig.dblClickDelay = 1000; +}; +goog.inherits(i18n.input.chrome.inputview.elements.content.SpaceKey, + i18n.input.chrome.inputview.elements.content.FunctionalKey); +var SpaceKey = i18n.input.chrome.inputview.elements.content.SpaceKey; + + +/** @override */ +SpaceKey.prototype.createDom = function() { + goog.base(this, 'createDom'); + + goog.dom.classlist.remove(this.bgElem, + i18n.input.chrome.inputview.Css.SPECIAL_KEY_BG); +}; + + +/** + * Gets the character. + * + * @return {string} The character. + */ +SpaceKey.prototype.getCharacter = function() { + if (this.characters_) { + // The index is based on the characters in order: + // 0: Default + // 1: Shift + // 2: ALTGR + // 3: SHIFT + ALTGR + var index = this.stateManager_.hasState(i18n.input.chrome.inputview. + StateType.SHIFT) ? 1 : 0 + this.stateManager_.hasState( + i18n.input.chrome.inputview.StateType.ALTGR) ? 2 : 0; + if (this.characters_.length > index && this.characters_[index]) { + return this.characters_[index]; + } + } + return ' '; +}; + + +/** + * Updates the title on the space key. + * + * @param {string} title . + * @param {boolean} visible True to set title visible. + */ +SpaceKey.prototype.updateTitle = function(title, visible) { + if (this.textElem) { + this.text = title; + goog.dom.setTextContent(this.textElem, visible ? title : ''); + goog.dom.classlist.add(this.textElem, + i18n.input.chrome.inputview.Css.TITLE); + } +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/switcherkey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/switcherkey.js new file mode 100644 index 0000000..978887e --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/switcherkey.js @@ -0,0 +1,83 @@ +// 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.SwitcherKey'); + +goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey'); + + + +goog.scope(function() { +var FunctionalKey = i18n.input.chrome.inputview.elements.content.FunctionalKey; + + + +/** + * The switcher key which can lead user to a new keyset. + * + * @param {string} id The id. + * @param {!i18n.input.chrome.inputview.elements.ElementType} type The element + * type. + * @param {string} text The text. + * @param {string} iconCssClass The css class for the icon. + * @param {string} toKeyset The keyset id. + * @param {string} toKeysetName The name of the keyset. + * @param {boolean} record True to record the keyset as the default. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.content.FunctionalKey} + */ +i18n.input.chrome.inputview.elements.content.SwitcherKey = function(id, type, + text, iconCssClass, toKeyset, toKeysetName, record, opt_eventTarget) { + goog.base(this, id, type, text, iconCssClass, opt_eventTarget); + + /** + * The id of the key set to go after this switcher key is pressed. + * + * @type {string} + */ + this.toKeyset = toKeyset; + + /** + * The name of the keyset. + * + * @type {string} + */ + this.toKeysetName = toKeysetName; + + /** + * True to record this keyset and brings it back next time. + * + * @type {boolean} + */ + this.record = record; +}; +goog.inherits(i18n.input.chrome.inputview.elements.content.SwitcherKey, + FunctionalKey); +var SwitcherKey = i18n.input.chrome.inputview.elements.content.SwitcherKey; + + +/** @override */ +SwitcherKey.prototype.createDom = function() { + goog.base(this, 'createDom'); + + this.setAriaLabel(this.getChromeVoxMessage()); +}; + + +/** @override */ +SwitcherKey.prototype.getChromeVoxMessage = function() { + return chrome.i18n.getMessage('SWITCH_TO') + this.toKeysetName; +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/content/tabbarkey.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/tabbarkey.js new file mode 100644 index 0000000..806ef19 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/content/tabbarkey.js @@ -0,0 +1,137 @@ +// 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.TabBarKey'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey'); +goog.require('i18n.input.chrome.message.Name'); +goog.require('i18n.input.chrome.message.Type'); + +goog.scope(function() { + +var Type = i18n.input.chrome.message.Type; +var Name = i18n.input.chrome.message.Name; +var Css = i18n.input.chrome.inputview.Css; + + + +/** + * The Tabbar key + * + * @param {string} id The id. + * @param {!i18n.input.chrome.inputview.elements.ElementType} type The element + * type. + * @param {string} text The text. + * @param {string} iconCssClass The css class for the icon. + * @param {number} toCategory The category the tabbar key represents. + * @param {i18n.input.chrome.inputview.StateManager} stateManager + * The state manager. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.content.FunctionalKey} + */ +i18n.input.chrome.inputview.elements.content.TabBarKey = function(id, type, + text, iconCssClass, toCategory, stateManager, opt_eventTarget) { + goog.base(this, id, type, text, iconCssClass, opt_eventTarget); + + /** + * The id of the key set to go after this switcher key in menu is pressed. + * + * @type {number} + */ + this.toCategory = toCategory; + + /** + * The state manager. + * + * @type {i18n.input.chrome.inputview.StateManager} + */ + this.stateManager = stateManager; + + this.pointerConfig.stopEventPropagation = false; +}; +goog.inherits(i18n.input.chrome.inputview.elements.content.TabBarKey, + i18n.input.chrome.inputview.elements.content.FunctionalKey); +var TabBarKey = i18n.input.chrome.inputview.elements.content.TabBarKey; + + +/** + * The height of the bottom border + * + * @type {number} + * @private + */ +TabBarKey.prototype.BORDER_HEIGHT_ = 4; + + +/** @override */ +TabBarKey.prototype.createDom = function() { + goog.base(this, 'createDom'); + var dom = this.getDomHelper(); + var elem = this.getElement(); + goog.dom.classlist.remove(elem, Css.SOFT_KEY); + goog.dom.classlist.add(elem, Css.EMOJI_TABBAR_SK); + goog.dom.classlist.remove(this.bgElem, Css.SPECIAL_KEY_BG); + goog.dom.classlist.add(this.bgElem, Css.EMOJI_TABBAR_KEY); + goog.dom.classlist.add(this.iconElem, Css.EMOJI_SWITCH); + this.createSeparator_(); +}; + + +/** @override */ +TabBarKey.prototype.resize = function(width, + height) { + goog.base(this, 'resize', width, height); + this.tableCell.style.width = this.availableWidth + 'px'; + this.tableCell.style.height = this.availableHeight - + this.BORDER_HEIGHT_ + 'px'; + this.sepTableCell.style.height = this.availableHeight - + this.BORDER_HEIGHT_ + 'px'; + this.separator.style.height = this.availableHeight * 0.32 + 'px'; +}; + + +/** + * Create the separator for the tabbar key. + * + * @private + */ +TabBarKey.prototype.createSeparator_ = function() { + var dom = this.getDomHelper(); + var elem = this.getElement(); + this.sepTableCell = dom.createDom(goog.dom.TagName.DIV, Css.TABLE_CELL); + this.separator = dom.createDom(goog.dom.TagName.DIV, + Css.CANDIDATE_SEPARATOR); + this.separator.style.height = Math.floor(this.height * 0.32) + 'px'; + dom.appendChild(this.sepTableCell, this.separator); + dom.appendChild(this.bgElem, this.sepTableCell); +}; + + +/** + * Update the border. + * + * @param {number} categoryID the categoryID. + */ +TabBarKey.prototype.updateBorder = function(categoryID) { + if (categoryID == this.toCategory) { + goog.dom.classlist.add(this.bgElem, Css.EMOJI_TABBAR_KEY_HIGHLIGHT); + goog.dom.classlist.add(this.iconElem, Css.EMOJI_SWITCH_HIGHLIGHT); + } else { + goog.dom.classlist.remove(this.bgElem, Css.EMOJI_TABBAR_KEY_HIGHLIGHT); + goog.dom.classlist.remove(this.iconElem, Css.EMOJI_SWITCH_HIGHLIGHT); + } +}; +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/element.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/element.js new file mode 100644 index 0000000..467b596 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/element.js @@ -0,0 +1,187 @@ +// 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.Element'); + +goog.require('goog.dom.classlist'); +goog.require('goog.events.EventHandler'); +goog.require('goog.style'); +goog.require('goog.ui.Component'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.PointerConfig'); + + +goog.scope(function() { + + + +/** + * The abstract class for element in input view keyboard. + * + * @param {string} id The id. + * @param {!i18n.input.chrome.inputview.elements.ElementType} type The element + * type. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @constructor + * @extends {goog.ui.Component} + */ +i18n.input.chrome.inputview.elements.Element = function(id, type, + opt_eventTarget) { + goog.base(this); + this.setParentEventTarget(opt_eventTarget || null); + + /** + * The id of the element. + * + * @type {string} + */ + this.id = id; + + /** + * The type of the element. + * + * @type {!i18n.input.chrome.inputview.elements.ElementType} + */ + this.type = type; + + /** + * The display of the element. + * + * @type {string} + * @private + */ + this.display_ = ''; + + /** + * The event handler. + * + * @type {!goog.events.EventHandler} + */ + this.handler = new goog.events.EventHandler(this); + + /** + * The configuration for the pointer. + * + * @type {!i18n.input.chrome.inputview.PointerConfig} + */ + this.pointerConfig = new i18n.input.chrome.inputview.PointerConfig(false, + false, false); +}; +goog.inherits(i18n.input.chrome.inputview.elements.Element, goog.ui.Component); +var Element = i18n.input.chrome.inputview.elements.Element; + + +/** + * The width of the element. + * + * @type {number} + */ +Element.prototype.width; + + +/** + * The height of the element. + * + * @type {number} + */ +Element.prototype.height; + + +/** + * Resizes the element. + * + * @param {number} width The total width. + * @param {number} height The total height. + */ +Element.prototype.resize = function(width, height) { + this.width = width; + this.height = height; +}; + + +/** @override */ +Element.prototype.createDom = function() { + goog.base(this, 'createDom'); + + this.getElement().id = this.id; + this.getElement()['view'] = this; +}; + + +/** @override */ +Element.prototype.enterDocument = function() { + goog.base(this, 'enterDocument'); + + this.display_ = this.getElement().style.display; +}; + + +/** + * Whether the element is visible. + * + * @return {boolean} True if the element is visible. + */ +Element.prototype.isVisible = function() { + return goog.style.isElementShown(this.getElement()); +}; + + +/** + * Sets the visibility of the element. + * + * @param {boolean} visibility True if the element is visible. + */ +Element.prototype.setVisible = function(visibility) { + this.getElement().style.display = visibility ? this.display_ : 'none'; +}; + + +/** + * Updates the element. + */ +Element.prototype.update = function() { + this.setHighlighted(false); + for (var i = 0; i < this.getChildCount(); i++) { + var child = /** @type {!Element} */ ( + this.getChildAt(i)); + child.update(); + } +}; + + +/** + * Sets the highlight of the soft key. + * + * @param {boolean} highlight True to set it to be highlighted. + */ +Element.prototype.setHighlighted = function( + highlight) { + if (highlight) { + goog.dom.classlist.add(this.getElement(), + i18n.input.chrome.inputview.Css.ELEMENT_HIGHLIGHT); + } else { + goog.dom.classlist.remove(this.getElement(), + i18n.input.chrome.inputview.Css.ELEMENT_HIGHLIGHT); + } +}; + + +/** @override */ +Element.prototype.disposeInternal = function() { + this.getElement()['view'] = null; + goog.dispose(this.handler); + + goog.base(this, 'disposeInternal'); +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/elementtype.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/elementtype.js new file mode 100644 index 0000000..ec6b0860 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/elementtype.js @@ -0,0 +1,67 @@ +// 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.ElementType'); + + +/** + * The element type. + * + * @enum {number} + */ +i18n.input.chrome.inputview.elements.ElementType = { + CHARACTER: 0, + FUNCTIONAL_KEY: 1, + KEYBOARD: 2, + LAYOUT_VIEW: 3, + LINEAR_LAYOUT: 4, + MODIFIER_KEY: 5, + CHARACTER_KEY: 6, + SOFT_KEY: 7, + SOFT_KEY_VIEW: 8, + VERTICAL_LAYOUT: 9, + CANDIDATE_VIEW: 10, + SPACE_KEY: 11, + ENTER_KEY: 12, + BACKSPACE_KEY: 13, + TAB_KEY: 14, + ARROW_UP: 15, + ARROW_DOWN: 16, + ARROW_LEFT: 17, + ARROW_RIGHT: 18, + HIDE_KEYBOARD_KEY: 19, + ALTDATA_VIEW: 20, + SWITCHER_KEY: 21, + COMPACT_KEY: 22, + CANVAS_VIEW: 23, + HANDWRITING_LAYOUT: 24, + MENU_VIEW: 25, + MENU_KEY: 26, + GLOBE_KEY: 27, + BACK_BUTTON: 28, + SHRINK_CANDIDATES: 29, + EXPAND_CANDIDATES: 30, + CANDIDATES_PAGE_UP: 31, + CANDIDATES_PAGE_DOWN: 32, + CANDIDATE: 33, + EXPANDED_CANDIDATE_VIEW: 34, + IME_SWITCH: 35, + HWT_PRIVACY_GOT_IT: 36, + MENU_ITEM: 37, + EMOJI_KEY: 38, + TAB_BAR_KEY: 39, + EXTENDED_LAYOUT: 40, + PAGE_INDICATOR: 41, + EN_SWITCHER: 42 +}; + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/extendedlayout.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/extendedlayout.js new file mode 100644 index 0000000..1b7e759 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/extendedlayout.js @@ -0,0 +1,229 @@ +// 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.layout.ExtendedLayout'); + +goog.require('goog.dom.classlist'); +goog.require('goog.style'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.Element'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.inputview.elements.Weightable'); + + +goog.scope(function() { +var Css = i18n.input.chrome.inputview.Css; + + + +/** + * The extended layout. Each of its children has the same width as its parent. + * It can be wider than its parent. + * + * @param {string} id The id. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @param {i18n.input.chrome.inputview.Css=} opt_iconCssClass The css class. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.Element} + * @implements {i18n.input.chrome.inputview.elements.Weightable} + */ +i18n.input.chrome.inputview.elements.layout.ExtendedLayout = function(id, + opt_eventTarget, opt_iconCssClass) { + goog.base(this, id, i18n.input.chrome.inputview.elements.ElementType. + EXTENDED_LAYOUT, opt_eventTarget); + + /** + * The icon Css class for the extendedlayout. + * + * @type {i18n.input.chrome.inputview.Css} + */ + this.iconCssClass = Css.LINEAR_LAYOUT; +}; +goog.inherits(i18n.input.chrome.inputview.elements.layout.ExtendedLayout, + i18n.input.chrome.inputview.elements.Element); +var ExtendedLayout = i18n.input.chrome.inputview.elements.layout.ExtendedLayout; + + +/** + * The height in weight unit. + * + * @type {number} + * @private + */ +ExtendedLayout.prototype.heightInWeight_ = 0; + + +/** + * The width in weight unit. + * + * @type {number} + * @private + */ +ExtendedLayout.prototype.widthInWeight_ = 0; + + +/** + * The time of transition for every transition distance. + * + * @private {number} + */ +ExtendedLayout.BASE_TRANSITION_DURATION_ = 0.2; + + +/** + * The transition distance used to calculate transition time. + * + * @private {number} + */ +ExtendedLayout.BASE_TRANSITION_DISTANCE_ = 100; + + +/** @override */ +ExtendedLayout.prototype.getHeightInWeight = function() { + return this.heightInWeight_; +}; + + +/** @override */ +ExtendedLayout.prototype.getWidthInWeight = function() { + return this.widthInWeight_; +}; + + +/** @override */ +ExtendedLayout.prototype.createDom = function() { + goog.base(this, 'createDom'); + this.elem = this.getElement(); + goog.dom.classlist.addAll(this.elem, + [this.iconCssClass, Css.EMOJI_FONT]); +}; + + +/** @override */ +ExtendedLayout.prototype.enterDocument = function() { + goog.base(this, 'enterDocument'); + this.calculate_(); +}; + + +/** @override */ +ExtendedLayout.prototype.resize = function(width, height) { + if (width == this.width && height == this.height) { + return; + } + for (var i = 0, len = this.getChildCount(); i < len; i++) { + var child = /** @type {i18n.input.chrome.inputview.elements.Element} */ ( + this.getChildAt(i)); + child.resize(width, height); + } + this.getElement().style.width = width * this.getChildCount(); + goog.base(this, 'resize', width * this.getChildCount(), height); +}; + + +/** + * Calculate the height and weight。 + * + * @private + */ +ExtendedLayout.prototype.calculate_ = function() { + for (var i = 0; i < this.getChildCount(); i++) { + var child = /** @type {i18n.input.chrome.inputview.elements.Weightable} */ ( + this.getChildAt(i)); + if (this.heightInWeight_ < child.getHeightInWeight()) { + this.heightInWeight_ = child.getHeightInWeight(); + } + this.widthInWeight_ += child.getWidthInWeight(); + } +}; + + +/** + * Switch to a page of the emojiSlider. + * + * @param {number} pageNum The page to switch to. + * @private + */ +ExtendedLayout.prototype.gotoPage_ = function(pageNum) { + var width = goog.style.getSize(this.getElement()).width; + var childNum = this.getChildCount(); + this.elem.style.marginLeft = 0 - width / childNum * pageNum; +}; + + +/** + * Slide to a position. + * + * @param {number} deltaX The slide distance of x-coordinate. + */ +ExtendedLayout.prototype.slide = function(deltaX) { + this.elem.style.transition = ''; + var marginLeft = goog.style.getMarginBox(this.elem).left + deltaX; + this.elem.style.marginLeft = marginLeft + 'px'; +}; + + +/** + * Adjust the marginleft to the beginning of a page. + * + * @param {number=} opt_distance The distance to adjust to. + * @return {number} The page to adjust to after calculation. + */ +ExtendedLayout.prototype.adjustMarginLeft = function(opt_distance) { + var childNum = this.getChildCount(); + var width = goog.style.getSize(this.elem).width / childNum; + var marginLeft = Math.abs(goog.style.getMarginBox(this.elem).left); + var prev = Math.floor(marginLeft / width); + var next = prev + 1; + var pageNum = 0; + if (opt_distance) { + if (opt_distance >= 0) { + pageNum = prev; + } else { + pageNum = next; + } + } else if (marginLeft - prev * width < next * width - marginLeft) { + pageNum = prev; + } else { + pageNum = next; + } + if (pageNum < 0) { + pageNum = 0; + } else if (pageNum >= childNum) { + pageNum = childNum - 1; + } + if (opt_distance) { + this.elem.style.transition = 'margin-left ' + + ExtendedLayout.BASE_TRANSITION_DURATION_ + 's'; + } else { + var transitionDuration = Math.abs(marginLeft - pageNum * width) / + ExtendedLayout.BASE_TRANSITION_DISTANCE_ * + ExtendedLayout.BASE_TRANSITION_DURATION_; + this.elem.style.transition = 'margin-left ' + + transitionDuration + 's ease-in'; + } + this.gotoPage_(pageNum); + return pageNum; +}; + + +/** + * Update the category. + * + * @param {number} pageNum The page number to switch to. + */ +ExtendedLayout.prototype.updateCategory = function(pageNum) { + this.elem.style.transition = ''; + this.gotoPage_(pageNum); +}; +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/handwritinglayout.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/handwritinglayout.js new file mode 100644 index 0000000..eb24e72 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/handwritinglayout.js @@ -0,0 +1,121 @@ +// 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.layout.HandwritingLayout'); + +goog.require('goog.dom.classlist'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.Element'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.inputview.elements.Weightable'); + + +goog.scope(function() { + + + +/** + * The linear layout. + * + * @param {string} id The id. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.Element} + * @implements {i18n.input.chrome.inputview.elements.Weightable} + */ +i18n.input.chrome.inputview.elements.layout.HandwritingLayout = function(id, + opt_eventTarget) { + goog.base(this, id, i18n.input.chrome.inputview.elements.ElementType. + HANDWRITING_LAYOUT, opt_eventTarget); +}; +goog.inherits(i18n.input.chrome.inputview.elements.layout.HandwritingLayout, + i18n.input.chrome.inputview.elements.Element); +var HandwritingLayout = + i18n.input.chrome.inputview.elements.layout.HandwritingLayout; + + +/** + * The height in weight unit. + * + * @type {number} + * @private + */ +HandwritingLayout.prototype.heightInWeight_ = 0; + + +/** + * The width in weight unit. + * + * @type {number} + * @private + */ +HandwritingLayout.prototype.widthInWeight_ = 0; + + +/** @override */ +HandwritingLayout.prototype.createDom = function() { + goog.base(this, 'createDom'); + + goog.dom.classlist.add(this.getElement(), i18n.input.chrome.inputview.Css. + HANDWRITING_LAYOUT); +}; + + +/** @override */ +HandwritingLayout.prototype.enterDocument = function() { + goog.base(this, 'enterDocument'); + + this.calculate_(); +}; + + +/** + * Gets the first child. + * + * @private + */ +HandwritingLayout.prototype.calculate_ = function() { + var child = /** @type {i18n.input.chrome.inputview.elements.Weightable} */ ( + this.getChildAt(0)); + this.heightInWeight_ = child.getHeightInWeight(); + this.widthInWeight_ = child.getWidthInWeight(); +}; + + +/** @override */ +HandwritingLayout.prototype.getHeightInWeight = function() { + return this.heightInWeight_; +}; + + +/** @override */ +HandwritingLayout.prototype.getWidthInWeight = function() { + return this.widthInWeight_; +}; + + +/** @override */ +HandwritingLayout.prototype.resize = function(width, height) { + goog.base(this, 'resize', width, height); + for (var i = 0; i < this.getChildCount(); i++) { + var child = /** @type {i18n.input.chrome.inputview.elements.Element} */ ( + this.getChildAt(i)); + child.resize( + Math.ceil(width * child.getWidthInWeight() / this.widthInWeight_), + Math.ceil(height * child.getHeightInWeight() / this.heightInWeight_)); + // 85/140 = 0.6 + child.getElement().style.top = + Math.ceil(height * 0.6 / this.heightInWeight_); + } +}; +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/linearlayout.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/linearlayout.js new file mode 100644 index 0000000..f583c8e --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/linearlayout.js @@ -0,0 +1,137 @@ +// 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.layout.LinearLayout'); + +goog.require('goog.dom.classlist'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.Element'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.inputview.elements.Weightable'); +goog.require('i18n.input.chrome.inputview.util'); + + +goog.scope(function() { + + + +/** + * The linear layout. + * + * @param {string} id The id. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @param {i18n.input.chrome.inputview.Css=} opt_iconCssClass The css class. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.Element} + * @implements {i18n.input.chrome.inputview.elements.Weightable} + */ +i18n.input.chrome.inputview.elements.layout.LinearLayout = function(id, + opt_eventTarget, opt_iconCssClass) { + goog.base(this, id, i18n.input.chrome.inputview.elements.ElementType. + LINEAR_LAYOUT, opt_eventTarget); + + /** + * The icon Css class for the linearlayout + * + * @type {i18n.input.chrome.inputview.Css} + */ + this.iconCssClass = opt_iconCssClass || + i18n.input.chrome.inputview.Css.LINEAR_LAYOUT; +}; +goog.inherits(i18n.input.chrome.inputview.elements.layout.LinearLayout, + i18n.input.chrome.inputview.elements.Element); +var LinearLayout = i18n.input.chrome.inputview.elements.layout.LinearLayout; + + +/** + * The height in weight unit. + * + * @type {number} + * @private + */ +LinearLayout.prototype.heightInWeight_ = 0; + + +/** + * The width in weight unit. + * + * @type {number} + * @private + */ +LinearLayout.prototype.widthInWeight_ = 0; + + +/** @override */ +LinearLayout.prototype.createDom = function() { + goog.base(this, 'createDom'); + goog.dom.classlist.add(this.getElement(), this.iconCssClass); +}; + + +/** @override */ +LinearLayout.prototype.enterDocument = function() { + goog.base(this, 'enterDocument'); + + this.calculate_(); +}; + + +/** + * Calculate all necessary information after enters the document. + * + * @private + */ +LinearLayout.prototype.calculate_ = function() { + for (var i = 0; i < this.getChildCount(); i++) { + var child = /** @type {i18n.input.chrome.inputview.elements.Weightable} */ ( + this.getChildAt(i)); + if (this.heightInWeight_ < child.getHeightInWeight()) { + this.heightInWeight_ = child.getHeightInWeight(); + } + this.widthInWeight_ += child.getWidthInWeight(); + } +}; + + +/** @override */ +LinearLayout.prototype.getHeightInWeight = function() { + return this.heightInWeight_; +}; + + +/** @override */ +LinearLayout.prototype.getWidthInWeight = function() { + return this.widthInWeight_; +}; + + +/** @override */ +LinearLayout.prototype.resize = function(width, height) { + goog.base(this, 'resize', width, height); + + var weightArray = []; + for (var i = 0; i < this.getChildCount(); i++) { + var child = /** @type {i18n.input.chrome.inputview.elements.Weightable} */ ( + this.getChildAt(i)); + weightArray.push(child.getWidthInWeight()); + } + var splitedWidth = i18n.input.chrome.inputview.util.splitValue(weightArray, + width); + for (var i = 0; i < this.getChildCount(); i++) { + var child = /** @type {i18n.input.chrome.inputview.elements.Element} */ ( + this.getChildAt(i)); + child.resize(splitedWidth[i], height); + } +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/softkeyview.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/softkeyview.js new file mode 100644 index 0000000..c2d1fec --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/softkeyview.js @@ -0,0 +1,150 @@ +// 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.layout.SoftKeyView'); + +goog.require('goog.dom.classlist'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.Element'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.inputview.elements.Weightable'); + + + +goog.scope(function() { + + + +/** + * The soft key view. + * + * @param {string} id The id. + * @param {number=} opt_widthInWeight The weight of width. + * @param {number=} opt_heightInWeight The weight of height. + * @param {string=} opt_condition The condition to control whether to render + * this soft key view. + * @param {string=} opt_giveWeightTo The id of the soft key view to give the + * weight if this one is not shown. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.Element} + * @implements {i18n.input.chrome.inputview.elements.Weightable} + */ +i18n.input.chrome.inputview.elements.layout.SoftKeyView = function(id, + opt_widthInWeight, opt_heightInWeight, opt_condition, + opt_giveWeightTo, opt_eventTarget) { + goog.base(this, id, i18n.input.chrome.inputview.elements.ElementType. + SOFT_KEY_VIEW, opt_eventTarget); + + /** + * The weight of the width. + * + * @type {number} + */ + this.widthInWeight = opt_widthInWeight || 1; + + /** + * The weight of the height. + * + * @type {number} + */ + this.heightInWeight = opt_heightInWeight || 1; + + /** + * The condition to control the visibility of this soft key view. + * + * @type {string} + */ + this.condition = opt_condition || ''; + + /** + * The id of another soft key view to give the weight if this + * one is not shown. + * + * @type {string} + */ + this.giveWeightTo = opt_giveWeightTo || ''; +}; +goog.inherits(i18n.input.chrome.inputview.elements.layout.SoftKeyView, + i18n.input.chrome.inputview.elements.Element); +var SoftKeyView = i18n.input.chrome.inputview.elements.layout.SoftKeyView; + + +/** + * The soft key bound to this view. + * + * @type {!i18n.input.chrome.inputview.elements.content.SoftKey} + */ +SoftKeyView.prototype.softKey; + + +/** + * The weight granted when certain condition happens and another soft key + * view is not shown, so its weight transfer here. + * + * @type {number} + */ +SoftKeyView.prototype.dynamicaGrantedWeight = 0; + + +/** @override */ +SoftKeyView.prototype.createDom = function() { + goog.base(this, 'createDom'); + + var elem = this.getElement(); + goog.dom.classlist.add(elem, i18n.input.chrome.inputview.Css.SOFT_KEY_VIEW); +}; + + +/** @override */ +SoftKeyView.prototype.getWidthInWeight = + function() { + return this.isVisible() ? this.widthInWeight + this.dynamicaGrantedWeight : 0; +}; + + +/** @override */ +SoftKeyView.prototype.getHeightInWeight = + function() { + return this.isVisible() ? this.heightInWeight : 0; +}; + + +/** @override */ +SoftKeyView.prototype.resize = function( + width, height) { + goog.base(this, 'resize', width, height); + + var elem = this.getElement(); + elem.style.width = width + 'px'; + elem.style.height = height + 'px'; + if (this.softKey) { + this.softKey.resize(width, height); + } +}; + + +/** + * Binds a soft key to this soft key view. + * + * @param {!i18n.input.chrome.inputview.elements.content.SoftKey} softKey The + * soft key. + */ +SoftKeyView.prototype.bindSoftKey = function( + softKey) { + this.softKey = softKey; + this.removeChildren(true); + this.addChild(softKey, true); +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/verticallayout.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/verticallayout.js new file mode 100644 index 0000000..3551ac0 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/layout/verticallayout.js @@ -0,0 +1,136 @@ +// 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.layout.VerticalLayout'); + +goog.require('goog.dom.classlist'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.Element'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); +goog.require('i18n.input.chrome.inputview.elements.Weightable'); +goog.require('i18n.input.chrome.inputview.util'); + + +goog.scope(function() { + + + +/** + * The vertical layout. + * + * @param {string} id The id. + * @param {goog.events.EventTarget=} opt_eventTarget The event target. + * @param {!i18n.input.chrome.inputview.elements.ElementType=} opt_type The sub + * type. + * @constructor + * @extends {i18n.input.chrome.inputview.elements.Element} + * @implements {i18n.input.chrome.inputview.elements.Weightable} + */ +i18n.input.chrome.inputview.elements.layout.VerticalLayout = function(id, + opt_eventTarget, opt_type) { + var type = opt_type || i18n.input.chrome.inputview.elements.ElementType. + VERTICAL_LAYOUT; + goog.base(this, id, type, opt_eventTarget); +}; +goog.inherits(i18n.input.chrome.inputview.elements.layout.VerticalLayout, + i18n.input.chrome.inputview.elements.Element); +var VerticalLayout = i18n.input.chrome.inputview.elements.layout.VerticalLayout; + + +/** + * The height in weight unit. + * + * @type {number} + * @private + */ +VerticalLayout.prototype.heightInWeight_ = 0; + + +/** + * The width in weight unit. + * + * @type {number} + * @private + */ +VerticalLayout.prototype.widthInWeight_ = 0; + + +/** @override */ +VerticalLayout.prototype.createDom = function() { + goog.base(this, 'createDom'); + + goog.dom.classlist.add(this.getElement(), + i18n.input.chrome.inputview.Css.VERTICAL_LAYOUT); +}; + + +/** @override */ +VerticalLayout.prototype.enterDocument = + function() { + goog.base(this, 'enterDocument'); + + this.calculate_(); +}; + + +/** + * Calculate all necessary information after enters the document. + * + * @private + */ +VerticalLayout.prototype.calculate_ = function() { + for (var i = 0; i < this.getChildCount(); i++) { + var child = /** @type {i18n.input.chrome.inputview.elements.Weightable} */ ( + this.getChildAt(i)); + if (this.widthInWeight_ < child.getWidthInWeight()) { + this.widthInWeight_ = child.getWidthInWeight(); + } + this.heightInWeight_ += child.getHeightInWeight(); + } +}; + + +/** @override */ +VerticalLayout.prototype.getHeightInWeight = function() { + return this.heightInWeight_; +}; + + +/** @override */ +VerticalLayout.prototype.getWidthInWeight = function() { + return this.widthInWeight_; +}; + + +/** @override */ +VerticalLayout.prototype.resize = function( + width, height) { + goog.base(this, 'resize', width, height); + + this.getElement().style.width = width + 'px'; + var weightArray = []; + for (var i = 0; i < this.getChildCount(); i++) { + var child = /** @type {i18n.input.chrome.inputview.elements.Weightable} */ ( + this.getChildAt(i)); + weightArray.push(child.getHeightInWeight()); + } + var splitedHeight = i18n.input.chrome.inputview.util.splitValue(weightArray, + height); + for (var i = 0; i < this.getChildCount(); i++) { + var child = /** @type {i18n.input.chrome.inputview.elements.Element} */ ( + this.getChildAt(i)); + child.resize(width, splitedHeight[i]); + } +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/elements/weightable.js b/third_party/google_input_tools/src/chrome/os/inputview/elements/weightable.js new file mode 100644 index 0000000..7555c75 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/elements/weightable.js @@ -0,0 +1,44 @@ +// 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.Weightable'); + + + +/** + * Interface defines the weight operation to an element which is computed + * with weight. + * + * @interface + */ +i18n.input.chrome.inputview.elements.Weightable = function() {}; + + +/** + * Gets the width of the element in weight. + * + * @return {number} The width. + */ +i18n.input.chrome.inputview.elements.Weightable.prototype.getWidthInWeight = + goog.abstractMethod; + + +/** + * Gets the height of the element in weight. + * + * @return {number} The height. + */ +i18n.input.chrome.inputview.elements.Weightable.prototype.getHeightInWeight = + goog.abstractMethod; + + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/emojitype.js b/third_party/google_input_tools/src/chrome/os/inputview/emojitype.js new file mode 100644 index 0000000..aa329a5 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/emojitype.js @@ -0,0 +1,33 @@ +// 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.EmojiType'); + + +/** + * The emoji category type. + * + * @enum {number} + */ +i18n.input.chrome.inputview.EmojiType = { + RECENT: 0, + FAVORITS: 1, + FACES: 2, + EMOTICON: 3, + SYMBOL: 4, + NATURE: 5, + PLACES: 6, + OBJECTS: 7 +}; + + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/events.js b/third_party/google_input_tools/src/chrome/os/inputview/events.js new file mode 100644 index 0000000..558b60d --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/events.js @@ -0,0 +1,253 @@ +// 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.events.ConfigLoadedEvent'); +goog.provide('i18n.input.chrome.inputview.events.ContextUpdateEvent'); +goog.provide('i18n.input.chrome.inputview.events.DragEvent'); +goog.provide('i18n.input.chrome.inputview.events.EventType'); +goog.provide('i18n.input.chrome.inputview.events.LayoutLoadedEvent'); +goog.provide('i18n.input.chrome.inputview.events.PointerEvent'); +goog.provide('i18n.input.chrome.inputview.events.SurroundingTextChangedEvent'); +goog.provide('i18n.input.chrome.inputview.events.SwipeEvent'); + +goog.require('goog.events'); +goog.require('goog.events.Event'); + + +goog.scope(function() { +var events = i18n.input.chrome.inputview.events; + + +/** + * Event types in input view keyboard. + * + * @enum {string} + */ +events.EventType = { + CLICK: goog.events.getUniqueId('c'), + CONFIG_LOADED: goog.events.getUniqueId('cl'), + DOUBLE_CLICK: goog.events.getUniqueId('dc'), + DOUBLE_CLICK_END: goog.events.getUniqueId('dce'), + DRAG: goog.events.getUniqueId('dg'), + LAYOUT_LOADED: goog.events.getUniqueId('ll'), + LONG_PRESS: goog.events.getUniqueId('lp'), + LONG_PRESS_END: goog.events.getUniqueId('lpe'), + POINTER_DOWN: goog.events.getUniqueId('pd'), + POINTER_UP: goog.events.getUniqueId('pu'), + POINTER_OVER: goog.events.getUniqueId('po'), + POINTER_OUT: goog.events.getUniqueId('po'), + SETTINGS_READY: goog.events.getUniqueId('sr'), + SURROUNDING_TEXT_CHANGED: goog.events.getUniqueId('stc'), + SWIPE: goog.events.getUniqueId('s'), + CONTEXT_UPDATE: goog.events.getUniqueId('cu'), + CONTEXT_FOCUS: goog.events.getUniqueId('cf'), + CONTEXT_BLUR: goog.events.getUniqueId('cb'), + VISIBILITY_CHANGE: goog.events.getUniqueId('vc'), + MODEL_UPDATE: goog.events.getUniqueId('mu') +}; + + + +/** + * The event when the data is loaded complete. + * + * @param {!Object} data The layout data. + * @constructor + * @extends {goog.events.Event} + */ +events.LayoutLoadedEvent = function(data) { + goog.base(this, events.EventType.LAYOUT_LOADED); + + /** + * The layout data. + * + * @type {!Object} + */ + this.data = data; +}; +goog.inherits(events.LayoutLoadedEvent, goog.events.Event); + + + +/** + * The event when the configuration is loaded complete. + * + * @param {!Object} data The configuration data. + * @constructor + * @extends {goog.events.Event} + */ +events.ConfigLoadedEvent = function(data) { + goog.base(this, events.EventType.CONFIG_LOADED); + + /** + * The configuration data. + * + * @type {!Object} + */ + this.data = data; +}; +goog.inherits(events.ConfigLoadedEvent, goog.events.Event); + + + +/** + * The pointer event. + * + * @param {i18n.input.chrome.inputview.elements.Element} view . + * @param {events.EventType} type . + * @param {Node} target The event target. + * @param {number} x . + * @param {number} y . + * @param {number=} opt_timestamp The timestamp of a pointer event. + * @constructor + * @extends {goog.events.Event} + */ +events.PointerEvent = function(view, type, target, x, y, opt_timestamp) { + goog.base(this, type, target); + + /** + * The view. + * + * @type {i18n.input.chrome.inputview.elements.Element} + */ + this.view = view; + + /** + * The x-coordinate. + * + * @type {number} + */ + this.x = x; + + /** + * The y-coordinate. + * + * @type {number} + */ + this.y = y; + + /** + * The timestamp. + * + * @type {number} + */ + this.timestamp = opt_timestamp || 0; +}; +goog.inherits(events.PointerEvent, goog.events.Event); + + + +/** + * The swipe event. + * + * @param {i18n.input.chrome.inputview.elements.Element} view . + * @param {number} direction See SwipeDirection in pointer handler. + * @param {Node} target The event target. + * @param {number} x . + * @param {number} y . + * @constructor + * @extends {events.PointerEvent} + */ +events.SwipeEvent = function(view, direction, target, x, y) { + goog.base(this, view, events.EventType.SWIPE, + target, x, y); + + /** + * The direction. + * + * @type {number} + */ + this.direction = direction; +}; +goog.inherits(events.SwipeEvent, events.PointerEvent); + + + +/** + * The drag event. + * + * @param {i18n.input.chrome.inputview.elements.Element} view . + * @param {number} direction See SwipeDirection in pointer handler. + * @param {Node} target The event target. + * @param {number} x . + * @param {number} y . + * @param {number} deltaX The drag distance of x-coordinate. + * @param {number} deltaY The drag distance of y-coordinate. + * @constructor + * @extends {events.PointerEvent} + */ +events.DragEvent = function(view, direction, target, x, y, deltaX, deltaY) { + goog.base(this, view, events.EventType.DRAG, + target, x, y); + /** + * The direction + * + * @type {number} + */ + this.direction = direction; + + /** + * The value of deltaX + * + * @type {number} + */ + this.deltaX = deltaX; + + /** + * The value of deltaY + * + * @type {number} + */ + this.deltaY = deltaY; +}; +goog.inherits(events.DragEvent, events.PointerEvent); + + + +/** + * The event when the surrounding text is changed. + * + * @param {string} text The surrounding text. + * @constructor + * @extends {goog.events.Event} + */ +events.SurroundingTextChangedEvent = function(text) { + goog.base(this, events.EventType.SURROUNDING_TEXT_CHANGED); + + /** @type {string} */ + this.text = text; +}; +goog.inherits(events.SurroundingTextChangedEvent, goog.events.Event); + + + +/** + * The event when context is updated. + * + * @param {string} compositionText . + * @param {string} committedText . + * @constructor + * @extends {goog.events.Event} + */ +events.ContextUpdateEvent = function(compositionText, committedText) { + goog.base(this, events.EventType.CONTEXT_UPDATE); + + /** @type {string} */ + this.compositionText = compositionText; + + /** @type {string} */ + this.committedText = committedText; +}; +goog.inherits(events.ContextUpdateEvent, goog.events.Event); + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/events/keycodes.js b/third_party/google_input_tools/src/chrome/os/inputview/events/keycodes.js new file mode 100644 index 0000000..fc05ac6 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/events/keycodes.js @@ -0,0 +1,313 @@ +// 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.events.KeyCodes'); + + +/** + * W3C Document Object Model (DOM) Level 3 Keyboard Events key codes + * Specification. + * @see http://www.w3.org/TR/DOM-Level-3-Events/#key-value-tables + * + * @enum {string} + */ +i18n.input.chrome.inputview.events.KeyCodes = { + // Special Key Values + UNIDENTIFIED: 'Unidentified', + + // Letter Key + BACK_QUOTE: 'Backquote', + KEY_A: 'KeyA', + KEY_B: 'KeyB', + KEY_C: 'KeyC', + KEY_D: 'KeyD', + KEY_E: 'KeyE', + KEY_F: 'KeyF', + KEY_G: 'KeyG', + KEY_H: 'KeyH', + KEY_I: 'KeyI', + KEY_J: 'KeyJ', + KEY_K: 'KeyK', + KEY_L: 'KeyL', + KEY_M: 'KeyM', + KEY_N: 'KeyN', + KEY_O: 'KeyO', + KEY_P: 'KeyP', + KEY_Q: 'KeyQ', + KEY_R: 'KeyR', + KEY_S: 'KeyS', + KEY_T: 'KeyT', + KEY_U: 'KeyU', + KEY_V: 'KeyV', + KEY_W: 'KeyW', + KEY_X: 'KeyX', + KEY_Y: 'KeyY', + KEY_Z: 'KeyZ', + + // Number keys + DIGIT_0: 'Digit0', + DIGIT_1: 'Digit1', + DIGIT_2: 'Digit2', + DIGIT_3: 'Digit3', + DIGIT_4: 'Digit4', + DIGIT_5: 'Digit5', + DIGIT_6: 'Digit6', + DIGIT_7: 'Digit7', + DIGIT_8: 'Digit8', + DIGIT_9: 'Digit9', + + // Modifier keys + ALT: 'Alt', + ALT_GRAPH: 'AltGraph', + ALT_LEFT: 'AltLeft', + ALT_RIGHT: 'AltRight', + CAPS_LOCK: 'CapsLock', + CONTROL: 'Control', + FN: 'Fn', + FN_LOCK: 'FnLock', + HYPER: 'Hyper', + META: 'Meta', + NUM_LOCK: 'NumLock', + O_S: 'OS', + SHIFT: 'Shift', + SUPER: 'Super', + SYMBOL: 'Symbol', + SYMBOL_LOCK: 'SymbolLock', + + // Whitespace keys + ENTER: 'Enter', + SEPARATOR: 'Separator', + TAB: 'Tab', + SPACE: 'Space', + + // Navigation keys + ARROW_DOWN: 'ArrowDown', + ARROW_LEFT: 'ArrowLeft', + ARROW_RIGHT: 'ArrowRight', + ARROW_UP: 'ArrowUp', + END: 'End', + HOME: 'Home', + PAGE_DOWN: 'PageDown', + PAGE_UP: 'PageUp', + + // Editing keys + BACKSPACE: 'Backspace', + CLEAR: 'Clear', + COPY: 'Copy', + CR_SEL: 'CrSel', + CUT: 'Cut', + DELETE: 'Delete', + ERASE_EOF: 'EraseEof', + EX_SEL: 'ExSel', + INSERT: 'Insert', + PASTE: 'Paste', + REDO: 'Redo', + UNDO: 'Undo', + + // UI keys + ACCEPT: 'Accept', + AGAIN: 'Again', + ATTN: 'Attn', + CANCEL: 'Cancel', + CONTEXT_MENU: 'ContextMenu', + ESCAPE: 'Escape', + EXECUTE: 'Execute', + FIND: 'Find', + HELP: 'Help', + PAUSE: 'Pause', + PLAY: 'Play', + PROPS: 'Props', + SCROLL_LOCK: 'ScrollLock', + ZOOM_IN: 'ZoomIn', + ZOOM_OUT: 'ZoomOut', + + // Device keys + BRIGHTNESS_DOWN: 'BrightnessDown', + BRIGHTNESS_UP: 'BrightnessUp', + CAMERA: 'Camera', + EJECT: 'Eject', + LOG_OFF: 'LogOff', + POWER: 'Power', + POWER_OFF: 'PowerOff', + PRINT_SCREEN: 'PrintScreen', + HIBERNATE: 'Hibernate', + STANDBY: 'Standby', + WAKE_UP: 'WakeUp', + + // IME and composition keys + ALL_CANDIDATES: 'AllCandidates', + ALPHANUMERIC: 'Alphanumeric', + CODE_INPUT: 'CodeInput', + COMPOSE: 'Compose', + CONVERT: 'Convert', + FINAL_MODE: 'FinalMode', + GROUP_FIRST: 'GroupFirst', + GROUP_LAST: 'GroupLast', + GROUP_NEXT: 'GroupNext', + GROUP_PREVIOUS: 'GroupPrevious', + MODE_CHANGE: 'ModeChange', + NEXT_CANDIDATE: 'NextCandidate', + NON_CONVERT: 'NonConvert', + PREVIOUS_CANDIDATE: 'PreviousCandidate', + PROCESS: 'Process', + SINGLE_CANDIDATE: 'SingleCandidate', + + // Keys specific to korean keyboards + ROMAN_CHARACTERS: 'RomanCharacters', + HANGUL_MODE: 'HangulMode', + HANJA_MODE: 'HanjaMode', + JUNJA_MODE: 'JunjaMode', + + // Keys specific to japanese keyboards + ZENKAKU: 'Zenkaku', + HANKAKU: 'Hankaku', + ZENKAKU_HANKAKU: 'ZenkakuHankaku', + KANA_MODE: 'KanaMode', + KANJI_MODE: 'KanjiMode', + HIRAGANA: 'Hiragana', + KATAKANA: 'Katakana', + HIRAGANA_KATAKANA: 'HiraganaKatakana', + EISU: 'Eisu', + + // General-purpose function keys + F1: 'F1', + F2: 'F2', + F3: 'F3', + F4: 'F4', + F5: 'F5', + F6: 'F6', + F7: 'F7', + F8: 'F8', + F9: 'F9', + F10: 'F10', + F11: 'F11', + F12: 'F12', + SOFT1: 'Soft1', + SOFT2: 'Soft2', + SOFT3: 'Soft3', + SOFT4: 'Soft4', + + // Mediamedia keys + CLOSE: 'Close', + MAIL_FORWARD: 'MailForward', + MAIL_REPLY: 'MailReply', + MAIL_SEND: 'MailSend', + MEDIA_PLAY_PAUSE: 'MediaPlayPause', + MEDIA_SELECT: 'MediaSelect', + MEDIA_STOP: 'MediaStop', + MEDIA_TRACK_NEXT: 'MediaTrackNext', + MEDIA_TRACK_PREVIOUS: 'MediaTrackPrevious', + NEW: 'New', + OPEN: 'Open', + PRINT: 'Print', + SAVE: 'Save', + SPELL_CHECK: 'SpellCheck', + VOLUME_DOWN: 'VolumeDown', + VOLUME_UP: 'VolumeUp', + VOLUME_MUTE: 'VolumeMute', + + // Application keys + LAUNCH_CALCULATOR: 'LaunchCalculator', + LAUNCH_CALENDAR: 'LaunchCalendar', + LAUNCH_MAIL: 'LaunchMail', + LAUNCH_MEDIA_PLAYER: 'LaunchMediaPlayer', + LAUNCH_MUSIC_PLAYER: 'LaunchMusicPlayer', + LAUNCH_MY_COMPUTER: 'LaunchMyComputer', + LAUNCH_SCREEN_SAVER: 'LaunchScreenSaver', + LAUNCH_SPREADSHEET: 'LaunchSpreadsheet', + LAUNCH_WEB_BROWSER: 'LaunchWebBrowser', + LAUNCH_WEB_CAM: 'LaunchWebCam', + LAUNCH_WORD_PROCESSOR: 'LaunchWordProcessor', + + // Browser keys + BROWSER_BACK: 'BrowserBack', + BROWSER_FAVORITES: 'BrowserFavorites', + BROWSER_FORWARD: 'BrowserForward', + BROWSER_HOME: 'BrowserHome', + BROWSER_REFRESH: 'BrowserRefresh', + BROWSER_SEARCH: 'BrowserSearch', + BROWSER_STOP: 'BrowserStop', + + // Media controller keys + AUDIO_BALANCE_LEFT: 'AudioBalanceLeft', + AUDIO_BALANCE_RIGHT: 'AudioBalanceRight', + AUDIO_BASS_BOOST_DOWN: 'AudioBassBoostDown', + AUDIO_BASS_BOOST_UP: 'AudioBassBoostUp', + AUDIO_FADER_FRONT: 'AudioFaderFront', + AUDIO_FADER_REAR: 'AudioFaderRear', + AUDIO_SURROUND_MODE_NEXT: 'AudioSurroundModeNext', + CHANNEL_DOWN: 'ChannelDown', + CHANNEL_UP: 'ChannelUp', + COLORF0_RED: 'ColorF0Red', + COLORF1_GREEN: 'ColorF1Green', + COLORF2_YELLOW: 'ColorF2Yellow', + COLORF3_BLUE: 'ColorF3Blue', + COLORF4_GREY: 'ColorF4Grey', + COLORF5_BROWN: 'ColorF5Brown', + CLOSED_CAPTION_TOGGLE: 'ClosedCaptionToggle', + DIMMER: 'Dimmer', + DISPLAY_SWAP: 'DisplaySwap', + EXIT: 'Exit', + FAVORITE_CLEAR0: 'FavoriteClear0', + FAVORITE_CLEAR1: 'FavoriteClear1', + FAVORITE_CLEAR2: 'FavoriteClear2', + FAVORITE_CLEAR3: 'FavoriteClear3', + FAVORITE_RECALL0: 'FavoriteRecall0', + FAVORITE_RECALL1: 'FavoriteRecall1', + FAVORITE_RECALL2: 'FavoriteRecall2', + FAVORITE_RECALL3: 'FavoriteRecall3', + FAVORITE_STORE0: 'FavoriteStore0', + FAVORITE_STORE1: 'FavoriteStore1', + FAVORITE_STORE2: 'FavoriteStore2', + FAVORITE_STORE3: 'FavoriteStore3', + GUIDE: 'Guide', + GUIDE_NEXT_DAY: 'GuideNextDay', + GUIDE_PREVIOUS_DAY: 'GuidePreviousDay', + INFO: 'Info', + INSTANT_REPLAY: 'InstantReplay', + LINK: 'Link', + LIST_PROGRAM: 'ListProgram', + LIVE_CONTENT: 'LiveContent', + LOCK: 'Lock', + MEDIA_APPS: 'MediaApps', + MEDIA_FAST_FORWARD: 'MediaFastForward', + MEDIA_LAST: 'MediaLast', + MEDIA_PAUSE: 'MediaPause', + MEDIA_PLAY: 'MediaPlay', + MEDIA_RECORD: 'MediaRecord', + MEDIA_REWIND: 'MediaRewind', + MEDIA_SKIP: 'MediaSkip', + NEXT_FAVORITE_CHANNEL: 'NextFavoriteChannel', + NEXT_USER_PROFILE: 'NextUserProfile', + ON_DEMAND: 'OnDemand', + PIN_P_DOWN: 'PinPDown', + PIN_P_MOVE: 'PinPMove', + PIN_P_TOGGLE: 'PinPToggle', + PIN_P_UP: 'PinPUp', + PLAY_SPEED_DOWN: 'PlaySpeedDown', + PLAY_SPEED_RESET: 'PlaySpeedReset', + PLAY_SPEED_UP: 'PlaySpeedUp', + RANDOM_TOGGLE: 'RandomToggle', + RC_LOW_BATTERY: 'RcLowBattery', + RECORD_SPEED_NEXT: 'RecordSpeedNext', + RF_BYPASS: 'RfBypass', + SCAN_CHANNELS_TOGGLE: 'ScanChannelsToggle ', + SCREEN_MODE_NEXT: 'ScreenModeNext', + SETTINGS: 'Settings', + SPLIT_SCREEN_TOGGLE: 'SplitScreenToggle', + SUBTITLE: 'Subtitle', + TELETEXT: 'Teletext', + VIDEO_MODE_NEXT: 'VideoModeNext', + WINK: 'Wink', + ZOOM_TOGGLE: 'ZoomToggle' +}; diff --git a/third_party/google_input_tools/src/chrome/os/inputview/globalsettings.js b/third_party/google_input_tools/src/chrome/os/inputview/globalsettings.js new file mode 100644 index 0000000..43ecf1e --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/globalsettings.js @@ -0,0 +1,235 @@ +// Copyright 2014 The Cloud Input Tools Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Defines the global settings variables for ITA. + */ + +goog.provide('i18n.input.common.GlobalSettings'); + +goog.require('goog.positioning.Corner'); +goog.require('goog.userAgent'); + + +/** + * The application name. + * + * @type {string} + */ +i18n.input.common.GlobalSettings.ApplicationName = 'jsapi'; + + +/** + * The help url for the help button on keyboard. If empty string, the help + * button will not be showed. + * + * @type {string} + */ +i18n.input.common.GlobalSettings.KeyboardHelpUrl = ''; + + +/** + * Whether show min/max button on keyboard. + * + * @type {boolean} + */ +i18n.input.common.GlobalSettings.KeyboardShowMinMax = false; + + +/** + * Whether to show status bar. + * + * @type {boolean} + */ +i18n.input.common.GlobalSettings.ShowStatusBar = true; + + +/** + * Whether to show google logo. + * + * @type {boolean} + */ +i18n.input.common.GlobalSettings.showGoogleLogo = false; + + +/** + * The shortcut key definition for statusbar toggle language command. + * + * @type {string} + */ +i18n.input.common.GlobalSettings.StatusBarToggleLanguageShortcut = 'shift'; + + +/** + * The shortcut key definition for statusbar toggle sbc/dbc command. + * + * @type {string} + */ +i18n.input.common.GlobalSettings.StatusBarToggleSbcShortcut = 'shift+space'; + + +/** + * The shortcut key definition for statusbar punctuation command. + * + * @type {string} + */ +i18n.input.common.GlobalSettings.StatusBarPunctuationShortcut = 'ctrl+.'; + + +/** + * Keyboard default location. + * + * @type {!goog.positioning.Corner} + */ +i18n.input.common.GlobalSettings.KeyboardDefaultLocation = + goog.positioning.Corner.BOTTOM_END; + + +/** + * Handwriting panel default location. + * + * @type {!goog.positioning.Corner} + */ +i18n.input.common.GlobalSettings.HandwritingDefaultLocation = + goog.positioning.Corner.BOTTOM_END; + + +/** + * Whether is offline mode. If true, IME will be switched to offline and all + * tracking (server ping, ga, csi, etc.) are disabled. + * TODO(shuchen): Later we will use this flag to switch to ITA offline decoder. + * + * @type {boolean} + */ +i18n.input.common.GlobalSettings.isOfflineMode = false; + + +/** + * Whether to sends the fake events when input box value is changed. + * + * @type {boolean} + */ +i18n.input.common.GlobalSettings.canSendFakeEvents = true; + + +/** + * No need to register handler in capture phase for IE8. + * + * @type {boolean} + */ +i18n.input.common.GlobalSettings.canListenInCaptureForIE8 = + !goog.userAgent.IE || goog.userAgent.isVersionOrHigher(9); + + +/** + * The chrome extension settings. + * + * @enum {string} + */ +i18n.input.common.GlobalSettings.chromeExtension = { + ACT_FLAG: 'IS_INPUT_ACTIVE', + ACTIVE_UI_IFRAME_ID: 'GOOGLE_INPUT_ACTIVE_UI', + APP_FLAG: 'GOOGLE_INPUT_NON_CHEXT_FLAG', + CHEXT_FLAG: 'GOOGLE_INPUT_CHEXT_FLAG', + INPUTTOOL: 'input', + INPUTTOOL_STAT: 'input_stat', + STATUS_BAR_IFRAME_ID: 'GOOGLE_INPUT_STATUS_BAR' +}; + + +/** + * @define {string} The name of the product which uses Google Input Tools API. + */ +i18n.input.common.GlobalSettings.BUILD_SOURCE = 'jsapi'; + + +/** + * @define {boolean} Whether uses XMLHttpRequest or not. + */ +i18n.input.common.GlobalSettings.ENABLE_XHR = false; + + +/** + * Whether enables the statistics for IME's status bar. + * TODO(shuchen): Investigates how to make sure status bar won't send + * duplicated metrics requests. And then we can remove this flag. + * + * @type {boolean} + */ +i18n.input.common.GlobalSettings.enableStatusBarMetrics = false; + + +/** + * Whether to show on-screen keyboard. + * false: Hides on-screen keyboard. + * true: Shows on-screen keyboard. + * + * @type {boolean} + */ +i18n.input.common.GlobalSettings.onScreenKeyboard = true; + + +/** + * Whether to enable personal dictionary or not. + * + * @type {boolean} + */ +i18n.input.common.GlobalSettings.enableUserDict = false; + + +/** + * Defines the max int value. + * + * @type {number} + */ +i18n.input.common.GlobalSettings.MAX_INT = 2147483647; + + +/** + * Whether enables the user prefs. + * + * @type {boolean} + */ +i18n.input.common.GlobalSettings.enableUserPrefs = true; + + +/** + * @define {boolean} UI wrapper by iframe. + */ +i18n.input.common.GlobalSettings.IFRAME_WRAPPER = false; + + +/** + * The CSS string. When create a new iframe wrapper, need to install css style + * in the iframe document. + * + * @type {string} + * + */ +i18n.input.common.GlobalSettings.css = ''; + + +/** + * Alternative image URL in CSS (e.g. Chrome extension needs a local path). + * + * @type {string} + */ +i18n.input.common.GlobalSettings.alternativeImageUrl = ''; + + +/** + * @define {boolean} Whether to enable Voice input. + */ +i18n.input.common.GlobalSettings.enableVoice = false; + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/handler/pointeractionbundle.js b/third_party/google_input_tools/src/chrome/os/inputview/handler/pointeractionbundle.js new file mode 100644 index 0000000..2d83e9f --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/handler/pointeractionbundle.js @@ -0,0 +1,447 @@ +// 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.handler.PointerActionBundle'); + +goog.require('goog.Timer'); +goog.require('goog.dom'); +goog.require('goog.events.EventTarget'); +goog.require('goog.events.EventType'); +goog.require('goog.math.Coordinate'); +goog.require('i18n.input.chrome.inputview.SwipeDirection'); +goog.require('i18n.input.chrome.inputview.events.DragEvent'); +goog.require('i18n.input.chrome.inputview.events.EventType'); +goog.require('i18n.input.chrome.inputview.events.PointerEvent'); +goog.require('i18n.input.chrome.inputview.events.SwipeEvent'); +goog.require('i18n.input.chrome.inputview.handler.SwipeState'); + + + +goog.scope(function() { + + + +/** + * The handler for long press. + * + * @param {!Node} target The target for this pointer event. + * @param {goog.events.EventTarget=} opt_parentEventTarget The parent event + * target. + * @constructor + * @extends {goog.events.EventTarget} + */ +i18n.input.chrome.inputview.handler.PointerActionBundle = function(target, + opt_parentEventTarget) { + goog.base(this); + this.setParentEventTarget(opt_parentEventTarget || null); + + /** + * The target. + * + * @type {i18n.input.chrome.inputview.elements.Element} + * @private + */ + this.view_ = this.getView_(target); + + /** + * The swipe offset. + * + * @type {!i18n.input.chrome.inputview.handler.SwipeState} + * @private + */ + this.swipeState_ = new i18n.input.chrome.inputview.handler.SwipeState(); +}; +goog.inherits(i18n.input.chrome.inputview.handler.PointerActionBundle, + goog.events.EventTarget); +var PointerActionBundle = i18n.input.chrome.inputview.handler. + PointerActionBundle; + + +/** + * The current target after the touch point is moved. + * + * @type {!Node | Element} + * @private + */ +PointerActionBundle.prototype.currentTarget_; + + +/** + * How many milli-seconds to evaluate a double click event. + * + * @type {number} + * @private + */ +PointerActionBundle.DOUBLE_CLICK_INTERVAL_ = 500; + + +/** + * The timer ID. + * + * @type {number} + * @private + */ +PointerActionBundle.prototype.longPressTimer_; + + +/** + * The minimum swipe distance. + * + * @type {number} + * @private + */ +PointerActionBundle.MINIMUM_SWIPE_DISTANCE_ = 20; + + +/** + * The timestamp of the pointer down. + * + * @type {number} + * @private + */ +PointerActionBundle.prototype.pointerDownTimeStamp_ = 0; + + +/** + * The timestamp of the pointer up. + * + * @type {number} + * @private + */ +PointerActionBundle.prototype.pointerUpTimeStamp_ = 0; + + +/** + * True if it is double clicking. + * + * @type {boolean} + * @private + */ +PointerActionBundle.prototype.isDBLClicking_ = false; + + +/** + * True if it is long pressing. + * + * @type {boolean} + * @private + */ +PointerActionBundle.prototype.isLongPressing_ = false; + + +/** + * True if it is flickering. + * + * @type {boolean} + * @private + */ +PointerActionBundle.prototype.isFlickering_ = false; + + +/** + * Gets the view. + * + * @param {Node} target . + * @return {i18n.input.chrome.inputview.elements.Element} . + * @private + */ +PointerActionBundle.prototype.getView_ = function(target) { + if (!target) { + return null; + } + var element = /** @type {!Element} */ (target); + var view = element['view']; + while (!view && element) { + view = element['view']; + element = goog.dom.getParentElement(element); + } + return view; +}; + + +/** + * Handles touchmove event for one target. + * + * @param {!Event} touchEvent . + */ +PointerActionBundle.prototype.handleTouchMove = function(touchEvent) { + var direction = 0; + var deltaX = this.swipeState_.previousX == 0 ? 0 : (touchEvent.pageX - + this.swipeState_.previousX); + var deltaY = this.swipeState_.previousY == 0 ? 0 : + (touchEvent.pageY - this.swipeState_.previousY); + this.swipeState_.offsetX += deltaX; + this.swipeState_.offsetY += deltaY; + this.dispatchEvent(new i18n.input.chrome.inputview.events.DragEvent( + this.view_, direction, /** @type {!Node} */ (touchEvent.target), + touchEvent.pageX, touchEvent.pageY, deltaX, deltaY)); + + var minimumSwipeDist = PointerActionBundle. + MINIMUM_SWIPE_DISTANCE_; + + if (this.swipeState_.offsetX > minimumSwipeDist) { + direction |= i18n.input.chrome.inputview.SwipeDirection.RIGHT; + this.swipeState_.offsetX = 0; + } else if (this.swipeState_.offsetX < -minimumSwipeDist) { + direction |= i18n.input.chrome.inputview.SwipeDirection.LEFT; + this.swipeState_.offsetX = 0; + } + + if (Math.abs(deltaY) > Math.abs(deltaX)) { + if (this.swipeState_.offsetY > minimumSwipeDist) { + direction |= i18n.input.chrome.inputview.SwipeDirection.DOWN; + this.swipeState_.offsetY = 0; + } else if (this.swipeState_.offsetY < -minimumSwipeDist) { + direction |= i18n.input.chrome.inputview.SwipeDirection.UP; + this.swipeState_.offsetY = 0; + } + } + + this.swipeState_.previousX = touchEvent.pageX; + this.swipeState_.previousY = touchEvent.pageY; + + if (direction > 0) { + // If there is any movement, cancel the longpress timer. + goog.Timer.clear(this.longPressTimer_); + this.dispatchEvent(new i18n.input.chrome.inputview.events.SwipeEvent( + this.view_, direction, /** @type {!Node} */ (touchEvent.target), + touchEvent.pageX, touchEvent.pageY)); + var currentTargetView = this.getView_(this.currentTarget_); + if (this.view_) { + this.isFlickering_ = !this.isLongPressing_ && !!(this.view_.pointerConfig. + flickerDirection & direction) && currentTargetView == this.view_; + } + } + + this.maybeSwitchTarget_(touchEvent); +}; + + +/** + * If the target is switched to a new one, sends out a pointer_over for the new + * target and sends out a pointer_out for the old target. + * + * @param {!Event | !goog.events.BrowserEvent} e . + * @private + */ +PointerActionBundle.prototype.maybeSwitchTarget_ = function(e) { + if (!this.isFlickering_) { + var pageOffset = this.getPageOffset_(e); + var actualTarget = document.elementFromPoint(pageOffset.x, pageOffset.y); + var currentTargetView = this.getView_(this.currentTarget_); + var actualTargetView = this.getView_(actualTarget); + if (currentTargetView != actualTargetView) { + if (currentTargetView) { + this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent( + currentTargetView, + i18n.input.chrome.inputview.events.EventType.POINTER_OUT, + this.currentTarget_, pageOffset.x, pageOffset.y)); + } + if (actualTargetView) { + this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent( + actualTargetView, + i18n.input.chrome.inputview.events.EventType.POINTER_OVER, + actualTarget, pageOffset.x, pageOffset.y)); + } + this.currentTarget_ = actualTarget; + } + } +}; + + +/** + * Handles pointer up, e.g., mouseup/touchend. + * + * @param {!goog.events.BrowserEvent} e The event. + */ +PointerActionBundle.prototype.handlePointerUp = function(e) { + goog.Timer.clear(this.longPressTimer_); + var pageOffset = this.getPageOffset_(e); + this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent( + this.view_, i18n.input.chrome.inputview.events.EventType.LONG_PRESS_END, + e.target, pageOffset.x, pageOffset.y)); + if (this.isDBLClicking_) { + this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent( + this.view_, i18n.input.chrome.inputview.events.EventType. + DOUBLE_CLICK_END, e.target, pageOffset.x, pageOffset.y)); + } else if (!(this.isLongPressing_ && this.view_.pointerConfig. + longPressWithoutPointerUp)) { + if (this.isLongPressing_) { + // If the finger didn't move but it triggers a longpress, it could cause + // a target switch, so checks it here. + this.maybeSwitchTarget_(e); + } + var view = this.getView_(this.currentTarget_); + var target = this.currentTarget_; + if (this.isFlickering_) { + view = this.view_; + target = e.target; + } + if (view) { + this.pointerUpTimeStamp_ = new Date().getTime(); + this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent( + view, i18n.input.chrome.inputview.events.EventType.POINTER_UP, + target, pageOffset.x, pageOffset.y, this.pointerUpTimeStamp_)); + } + } + if (this.getView_(this.currentTarget_) == this.view_) { + this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent( + this.view_, i18n.input.chrome.inputview.events.EventType.CLICK, + e.target, pageOffset.x, pageOffset.y)); + } + this.isDBLClicking_ = false; + this.isLongPressing_ = false; + this.isFlickering_ = false; + this.swipeState_.reset(); + + e.preventDefault(); + if (this.view_ && this.view_.pointerConfig.stopEventPropagation) { + e.stopPropagation(); + } +}; + + +/** + * Cancel double click recognition on this target. + */ +PointerActionBundle.prototype.cancelDoubleClick = function() { + this.pointerDownTimeStamp_ = 0; +}; + + +/** + * Handles pointer down, e.g., mousedown/touchstart. + * + * @param {!goog.events.BrowserEvent} e The event. + */ +PointerActionBundle.prototype.handlePointerDown = function(e) { + this.currentTarget_ = e.target; + goog.Timer.clear(this.longPressTimer_); + if (e.type != goog.events.EventType.MOUSEDOWN) { + // Don't trigger long press for mouse event. + this.maybeTriggerKeyDownLongPress_(e); + } + this.maybeHandleDBLClick_(e); + if (!this.isDBLClicking_) { + var pageOffset = this.getPageOffset_(e); + this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent( + this.view_, i18n.input.chrome.inputview.events.EventType.POINTER_DOWN, + e.target, pageOffset.x, pageOffset.y, this.pointerDownTimeStamp_)); + } + + if (this.view_ && this.view_.pointerConfig.preventDefault) { + e.preventDefault(); + } + if (this.view_ && this.view_.pointerConfig.stopEventPropagation) { + e.stopPropagation(); + } +}; + + +/** + * Gets the page offset from the event which may be mouse event or touch event. + * + * @param {!goog.events.BrowserEvent | !TouchEvent | !Event} e . + * @return {!goog.math.Coordinate} . + * @private + */ +PointerActionBundle.prototype.getPageOffset_ = function(e) { + if (e.pageX && e.pageY) { + return new goog.math.Coordinate(e.pageX, e.pageY); + } + + if (!e.getBrowserEvent) { + return new goog.math.Coordinate(0, 0); + } + + var nativeEvt = e.getBrowserEvent(); + if (nativeEvt.pageX && nativeEvt.pageY) { + return new goog.math.Coordinate(nativeEvt.pageX, nativeEvt.pageY); + } + + + var touchEventList = nativeEvt['changedTouches']; + if (!touchEventList || touchEventList.length == 0) { + touchEventList = nativeEvt['touches']; + } + if (touchEventList && touchEventList.length > 0) { + var touchEvent = touchEventList[0]; + return new goog.math.Coordinate(touchEvent.pageX, touchEvent.pageY); + } + + return new goog.math.Coordinate(0, 0); +}; + + +/** + * Maybe triggers the long press timer when pointer down. + * + * @param {!goog.events.BrowserEvent} e The event. + * @private + */ +PointerActionBundle.prototype.maybeTriggerKeyDownLongPress_ = function(e) { + if (this.view_ && (this.view_.pointerConfig.longPressWithPointerUp || + this.view_.pointerConfig.longPressWithoutPointerUp)) { + this.longPressTimer_ = goog.Timer.callOnce( + goog.bind(this.triggerLongPress_, this, e), + this.view_.pointerConfig.longPressDelay, this); + } +}; + + +/** + * Maybe handle the double click. + * + * @param {!goog.events.BrowserEvent} e . + * @private + */ +PointerActionBundle.prototype.maybeHandleDBLClick_ = function(e) { + if (this.view_ && this.view_.pointerConfig.dblClick) { + var timeInMs = new Date().getTime(); + var interval = this.view_.pointerConfig.dblClickDelay || + PointerActionBundle.DOUBLE_CLICK_INTERVAL_; + var nativeEvt = e.getBrowserEvent(); + if ((timeInMs - this.pointerDownTimeStamp_) < interval) { + this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent( + this.view_, i18n.input.chrome.inputview.events.EventType.DOUBLE_CLICK, + e.target, nativeEvt.pageX, nativeEvt.pageY)); + this.isDBLClicking_ = true; + } + this.pointerDownTimeStamp_ = timeInMs; + } +}; + + +/** + * Triggers long press event. + * + * @param {!goog.events.BrowserEvent} e The event. + * @private + */ +PointerActionBundle.prototype.triggerLongPress_ = function(e) { + var nativeEvt = e.getBrowserEvent(); + this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent( + this.view_, i18n.input.chrome.inputview.events.EventType.LONG_PRESS, + e.target, nativeEvt.pageX, nativeEvt.pageY)); + this.isLongPressing_ = true; +}; + + +/** @override */ +PointerActionBundle.prototype.disposeInternal = function() { + goog.dispose(this.longPressTimer_); + + goog.base(this, 'disposeInternal'); +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/handler/pointerhandler.js b/third_party/google_input_tools/src/chrome/os/inputview/handler/pointerhandler.js new file mode 100644 index 0000000..3e7be71 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/handler/pointerhandler.js @@ -0,0 +1,184 @@ +// 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.handler.PointerHandler'); + +goog.require('goog.Timer'); +goog.require('goog.events.EventHandler'); +goog.require('goog.events.EventTarget'); +goog.require('goog.events.EventType'); +goog.require('goog.math.Coordinate'); +goog.require('i18n.input.chrome.inputview.handler.PointerActionBundle'); + +goog.scope(function() { + + + +/** + * The pointer controller. + * + * @param {!Element=} opt_target . + * @constructor + * @extends {goog.events.EventTarget} + */ +i18n.input.chrome.inputview.handler.PointerHandler = function(opt_target) { + goog.base(this); + + /** + * The pointer handlers. + * + * @type {!Object.<number, !i18n.input.chrome.inputview.handler.PointerActionBundle>} + * @private + */ + this.pointerActionBundles_ = {}; + + /** + * The event handler. + * + * @type {!goog.events.EventHandler} + * @private + */ + this.eventHandler_ = new goog.events.EventHandler(this); + + var target = opt_target || document; + this.eventHandler_. + listen(target, [goog.events.EventType.MOUSEDOWN, + goog.events.EventType.TOUCHSTART], this.onPointerDown_, true). + listen(target, [goog.events.EventType.MOUSEUP, + goog.events.EventType.TOUCHEND], this.onPointerUp_, true). + listen(target, goog.events.EventType.TOUCHMOVE, this.onTouchMove_, + true); +}; +goog.inherits(i18n.input.chrome.inputview.handler.PointerHandler, + goog.events.EventTarget); +var PointerHandler = i18n.input.chrome.inputview.handler.PointerHandler; + + +/** + * The canvas class name. + * @const {string} + * @private + */ +PointerHandler.CANVAS_CLASS_NAME_ = 'ita-hwt-canvas'; + + +/** + * Mouse down tick, which is for delayed pointer up for tap action on touchpad. + * + * @private {Date} + */ +PointerHandler.prototype.mouseDownTick_ = null; + + +/** + * Event handler for previous mousedown or touchstart target. + * + * @private {i18n.input.chrome.inputview.handler.PointerActionBundle} + */ +PointerHandler.prototype.previousPointerActionBundle_ = null; + + +/** + * Creates a new pointer handler. + * + * @param {!Node} target . + * @return {!i18n.input.chrome.inputview.handler.PointerActionBundle} . + * @private + */ +PointerHandler.prototype.createPointerActionBundle_ = function(target) { + var uid = goog.getUid(target); + if (!this.pointerActionBundles_[uid]) { + this.pointerActionBundles_[uid] = new i18n.input.chrome.inputview.handler. + PointerActionBundle(target, this); + } + return this.pointerActionBundles_[uid]; +}; + + +/** + * Callback for mouse/touch down on the target. + * + * @param {!goog.events.BrowserEvent} e The event. + * @private + */ +PointerHandler.prototype.onPointerDown_ = function(e) { + var pointerActionBundle = this.createPointerActionBundle_( + /** @type {!Node} */ (e.target)); + if (this.previousPointerActionBundle_ && + this.previousPointerActionBundle_ != pointerActionBundle) { + this.previousPointerActionBundle_.cancelDoubleClick(); + } + this.previousPointerActionBundle_ = pointerActionBundle; + pointerActionBundle.handlePointerDown(e); + if (e.type == goog.events.EventType.MOUSEDOWN) { + this.mouseDownTick_ = new Date(); + } +}; + + +/** + * Callback for pointer out. + * + * @param {!goog.events.BrowserEvent} e The event. + * @private + */ +PointerHandler.prototype.onPointerUp_ = function(e) { + if (e.type == goog.events.EventType.MOUSEUP) { + // If mouseup happens too fast after mousedown, it may be a tap action on + // touchpad, so delay the pointer up action so user can see the visual + // flash. + if (this.mouseDownTick_ && new Date() - this.mouseDownTick_ < 10) { + goog.Timer.callOnce(this.onPointerUp_.bind(this, e), 50); + return; + } + } + var uid = goog.getUid(e.target); + var pointerActionBundle = this.pointerActionBundles_[uid]; + if (pointerActionBundle) { + pointerActionBundle.handlePointerUp(e); + } +}; + + +/** + * Callback for touchmove. + * + * @param {!goog.events.BrowserEvent} e The event. + * @private + */ +PointerHandler.prototype.onTouchMove_ = function(e) { + var touches = e.getBrowserEvent()['touches']; + if (!touches || touches.length == 0) { + return; + } + for (var i = 0; i < touches.length; i++) { + var uid = goog.getUid(touches[i].target); + var pointerActionBundle = this.pointerActionBundles_[uid]; + if (pointerActionBundle) { + pointerActionBundle.handleTouchMove(touches[i]); + } + } +}; + + +/** @override */ +PointerHandler.prototype.disposeInternal = function() { + for (var bundle in this.pointerActionBundles_) { + goog.dispose(bundle); + } + goog.dispose(this.eventHandler_); + + goog.base(this, 'disposeInternal'); +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/handler/swipestate.js b/third_party/google_input_tools/src/chrome/os/inputview/handler/swipestate.js new file mode 100644 index 0000000..4c2ee1e --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/handler/swipestate.js @@ -0,0 +1,61 @@ +// 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.handler.SwipeState'); + + + +/** + * The state for the swipe action. + * + * @constructor + */ +i18n.input.chrome.inputview.handler.SwipeState = function() { + /** + * The offset in x-axis. + * + * @type {number} + */ + this.offsetX = 0; + + /** + * The offset in y-axis. + * + * @type {number} + */ + this.offsetY = 0; + + /** + * The previous x coordinate. + * + * @type {number} + */ + this.previousX = 0; + + /** + * The previous y coordinate. + * + * @type {number} + */ + this.previousY = 0; +}; + + +/** + * Resets the state. + */ +i18n.input.chrome.inputview.handler.SwipeState.prototype.reset = + function() { + this.offsetX = this.offsetY = this.previousX = this.previousY = 0; +}; + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/hwt_css.js b/third_party/google_input_tools/src/chrome/os/inputview/hwt_css.js new file mode 100644 index 0000000..9b654cc --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/hwt_css.js @@ -0,0 +1,65 @@ +// 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"); +// + +/** + * @fileoverview Defines the class i18n.input.hwt.Css. + * @author fengyuan@google.com (Feng Yuan) + */ +goog.provide('i18n.input.hwt.css'); + + +/** + * CSS used for handwriting input pad. + * + * @enum {string} + */ +i18n.input.hwt.css = { + RTL: goog.getCssName('ita-hwt-rtl'), + LTR: goog.getCssName('ita-hwt-ltr'), + IME: goog.getCssName('ita-hwt-ime'), + IME_HOVER: goog.getCssName('ita-hwt-ime-hover'), + IME_OPAQUE: goog.getCssName('ita-hwt-ime-opaque'), + IME_ST: goog.getCssName('ita-hwt-ime-st'), + IME_INIT_OPAQUE: goog.getCssName('ita-hwt-ime-init-opaque'), + CLOSE: goog.getCssName('ita-hwt-close'), + GRIP: goog.getCssName('ita-hwt-grip'), + GRIP_HOVER: goog.getCssName('ita-hwt-grip-hover'), + CANVAS: goog.getCssName('ita-hwt-canvas'), + CANDIDATES: goog.getCssName('ita-hwt-candidates'), + CANDIDATE: goog.getCssName('ita-hwt-candidate'), + CANDIDATE_HOVER: goog.getCssName('ita-hwt-candidate-hover'), + SELECTED: goog.getCssName('ita-hwt-selected'), + DISABLED: goog.getCssName('ita-hwt-disabled'), + BUTTONS: goog.getCssName('ita-hwt-buttons'), + DIVIDER: goog.getCssName('ita-hwt-divider'), + BUTTON: goog.getCssName('ita-hwt-button'), + BACKSPACE: goog.getCssName('ita-hwt-backspace'), + BACKSPACE_IMG: goog.getCssName('ita-hwt-backspace-img'), + SPACE: goog.getCssName('ita-hwt-space'), + ENTER: goog.getCssName('ita-hwt-enter'), + ENTER_IMG: goog.getCssName('ita-hwt-enter-img'), + ENTER_IMG_DARK: goog.getCssName('ita-hwt-enter-img-dark'), + ENTER_IMG_WHITE: goog.getCssName('ita-hwt-enter-img-white'), + LANGUAGE: goog.getCssName('ita-hwt-language'), + CLEAR_TIME: goog.getCssName('ita-hwt-clear-time'), + INSERT_TIME: goog.getCssName('ita-hwt-insert-time'), + REMOTE_SPRITE: goog.getCssName('ita-kd-img'), + MAXIMIZED: goog.getCssName('ita-hwt-ime-full'), + JFK_BUTTON: goog.getCssName('ita-hwt-jfk'), + JFK_STANDARD: goog.getCssName('ita-hwt-jfk-standard'), + JFK_ACTION: goog.getCssName('ita-hwt-jfk-action'), + JFK_HOVER: goog.getCssName('ita-hwt-jfk-hover'), + BUTTER_BAR: goog.getCssName('ita-hwt-butterbar'), + SHOWN: goog.getCssName('shown') +}; diff --git a/third_party/google_input_tools/src/chrome/os/inputview/hwt_eventtype.js b/third_party/google_input_tools/src/chrome/os/inputview/hwt_eventtype.js new file mode 100644 index 0000000..85b894b --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/hwt_eventtype.js @@ -0,0 +1,96 @@ +// 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"); +// + +/** + * @fileoverview Handwriting event type. + * @author wuyingbing@google.com (Yingbing Wu) + */ + + +goog.provide('i18n.input.hwt.CandidateSelectEvent'); +goog.provide('i18n.input.hwt.CommitEvent'); +goog.provide('i18n.input.hwt.EventType'); + +goog.require('goog.events'); +goog.require('goog.events.Event'); + + +/** + * Handwriting event type to expose to external. + * + * @enum {string} + */ +i18n.input.hwt.EventType = { + BACKSPACE: goog.events.getUniqueId('b'), + CANDIDATE_SELECT: goog.events.getUniqueId('cs'), + COMMIT: goog.events.getUniqueId('c'), + COMMIT_START: goog.events.getUniqueId('hcs'), + COMMIT_END: goog.events.getUniqueId('hce'), + RECOGNITION_READY: goog.events.getUniqueId('rr'), + ENTER: goog.events.getUniqueId('e'), + HANDWRITING_CLOSED: goog.events.getUniqueId('hc'), + MOUSEUP: goog.events.getUniqueId('m'), + SPACE: goog.events.getUniqueId('s') +}; + + + +/** + * Candidate select event. + * + * @param {string} candidate The candidate. + * @constructor + * @extends {goog.events.Event} + */ +i18n.input.hwt.CandidateSelectEvent = function(candidate) { + goog.base(this, i18n.input.hwt.EventType.CANDIDATE_SELECT); + + /** + * The candidate. + * + * @type {string} + */ + this.candidate = candidate; +}; +goog.inherits(i18n.input.hwt.CandidateSelectEvent, goog.events.Event); + + + +/** + * Commit event. + * + * @param {string} text The text to commit. + * @param {number=} opt_back The number of characters to be deleted. + * @constructor + * @extends {goog.events.Event} + */ +i18n.input.hwt.CommitEvent = function(text, opt_back) { + goog.base(this, i18n.input.hwt.EventType.COMMIT); + + /** + * The text. + * + * @type {string} + */ + this.text = text; + + /** + * The number of characters to be deleted. + * + * @type {number} + */ + this.back = opt_back || 0; +}; +goog.inherits(i18n.input.hwt.CommitEvent, goog.events.Event); + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/hwt_util.js b/third_party/google_input_tools/src/chrome/os/inputview/hwt_util.js new file mode 100644 index 0000000..be95e26 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/hwt_util.js @@ -0,0 +1,74 @@ +// 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"); +// + +/** + * @fileoverview Defines the class i18n.input.hwt.util. + * @author fengyuan@google.com (Feng Yuan) + */ + +goog.provide('i18n.input.hwt.util'); + + +goog.require('goog.array'); +goog.require('goog.events.EventType'); +goog.require('i18n.input.common.dom'); + + +/** + * Listens MOUSEUP on page scope. + * + * @param {goog.events.EventHandler} eventHandler The event handler. + * @param {Document} topDocument The top document. + * @param {goog.events.EventType | !Array.<goog.events.EventType>} eventType + * The event type to listen. + * @param {Function} callback The callback method. + */ +i18n.input.hwt.util.listenPageEvent = function(eventHandler, topDocument, + eventType, callback) { + // Ideally we'd just listen for MOUSEUP on the canvas, but that + // doesn't work if the mouseup event happens outside the canvas, + // and in particular if it happens in an iframe. So we have to + // listen on the document and any embedded iframes. We'll also + // use this handler to cancel auto-repeat when holding down + // the backspace button, in case the user releases the button + // elsewhere. + eventHandler.listen(topDocument, goog.events.EventType.MOUSEUP, + callback, true); + goog.array.forEach(i18n.input.common.dom.getSameDomainDocuments(topDocument), + function(frameDoc) { + try { + // In IE and FF3.5, some iframe is not allowed to access. + // It throws exception when access the iframe property. + eventHandler.listen(frameDoc, goog.events.EventType.MOUSEUP, + callback, true); + } catch (e) {} + }); +}; + + +/** + * The default candidate map of handwriting pad. + * Key: language code. + * Value: candidate list. + * + * @type {!Object.<string, !Array.<string>>} + */ +i18n.input.hwt.util.DEFAULT_CANDIDATE_MAP = { + '': [',', '.', '?', '!', ':', '\'', '"', ';', '@'], + 'es': [',', '.', '¿', '?', '¡', '!', ':', '\'', '"'], + 'ja': [',', '。', '?', '!', ':', '「', '」', ';'], + 'zh-Hans': [',', '。', '?', '!', ':', '“', '”', ';'], + 'zh-Hant': [',', '。', '?', '!', ':', '「', '」', ';'] +}; + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/inputtoolcode.js b/third_party/google_input_tools/src/chrome/os/inputview/inputtoolcode.js new file mode 100644 index 0000000..db3ab03 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/inputtoolcode.js @@ -0,0 +1,369 @@ +// 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.lang.InputToolCode'); + + +/** + * Enumeration for input tool codes + * + * @enum {string} + */ +i18n.input.lang.InputToolCode = { + // For IME. + INPUTMETHOD_ARRAY92_CHINESE_TRADITIONAL: 'zh-hant-t-i0-array-1992', + INPUTMETHOD_CANGJIE82_CHINESE_SIMPLIFIED: 'zh-hans-t-i0-cangjie-1982', + INPUTMETHOD_CANGJIE82_CHINESE_TRADITIONAL: 'zh-hant-t-i0-cangjie-1982', + INPUTMETHOD_CANGJIE87_CHINESE_SIMPLIFIED: 'zh-hans-t-i0-cangjie-1987', + INPUTMETHOD_CANGJIE87_CHINESE_TRADITIONAL: 'zh-hant-t-i0-cangjie-1987', + INPUTMETHOD_CANGJIE87_QUICK_CHINESE_TRADITIONAL: + 'zh-hant-t-i0-cangjie-1987-x-m0-simplified', + INPUTMETHOD_CANTONESE_TRADITIONAL: 'yue-hant-t-i0-und', + INPUTMETHOD_DAYI88_CHINESE_TRADITIONAL: 'zh-hant-t-i0-dayi-1988', + INPUTMETHOD_PINYIN_CHINESE_SIMPLIFIED: 'zh-t-i0-pinyin', + INPUTMETHOD_PINYIN_CHINESE_TRADITIONAL: 'zh-hant-t-i0-pinyin', + INPUTMETHOD_HANGUL_KOREAN: 'ko-t-i0-und', + INPUTMETHOD_TRANSLITERATION_AMHARIC: 'am-t-i0-und', + INPUTMETHOD_TRANSLITERATION_ARABIC: 'ar-t-i0-und', + INPUTMETHOD_TRANSLITERATION_BELARUSIAN: 'be-t-i0-und', + INPUTMETHOD_TRANSLITERATION_BENGALI: 'bn-t-i0-und', + INPUTMETHOD_TRANSLITERATION_BULGARIAN: 'bg-t-i0-und', + INPUTMETHOD_TRANSLITERATION_DUTCH: 'nl-t-i0-und', + INPUTMETHOD_TRANSLITERATION_ENGLISH: 'en-t-i0-und', + INPUTMETHOD_TRANSLITERATION_FRENCH: 'fr-t-i0-und', + INPUTMETHOD_TRANSLITERATION_GERMAN: 'de-t-i0-und', + INPUTMETHOD_TRANSLITERATION_GREEK: 'el-t-i0-und', + INPUTMETHOD_TRANSLITERATION_GUJARATI: 'gu-t-i0-und', + INPUTMETHOD_TRANSLITERATION_HEBREW: 'he-t-i0-und', + INPUTMETHOD_TRANSLITERATION_HINDI: 'hi-t-i0-und', + INPUTMETHOD_TRANSLITERATION_HIRAGANA: 'ja-hira-t-i0-und', + INPUTMETHOD_TRANSLITERATION_ITALIAN: 'it-t-i0-und', + INPUTMETHOD_TRANSLITERATION_JAPANESE: 'ja-t-ja-hira-i0-und', + INPUTMETHOD_TRANSLITERATION_KANNADA: 'kn-t-i0-und', + INPUTMETHOD_TRANSLITERATION_MALAYALAM: 'ml-t-i0-und', + INPUTMETHOD_TRANSLITERATION_MARATHI: 'mr-t-i0-und', + INPUTMETHOD_TRANSLITERATION_NEPALI: 'ne-t-i0-und', + INPUTMETHOD_TRANSLITERATION_ORIYA: 'or-t-i0-und', + INPUTMETHOD_TRANSLITERATION_PERSIAN: 'fa-t-i0-und', + INPUTMETHOD_TRANSLITERATION_POLISH: 'pl-t-i0-und', + INPUTMETHOD_TRANSLITERATION_PORTUGUESE: 'pt-t-i0-und', + INPUTMETHOD_TRANSLITERATION_PORTUGUESE_BRRAZIL: 'pt-br-t-i0-und', + INPUTMETHOD_TRANSLITERATION_PORTUGUESE_PORTUGAL: 'pt-pt-t-i0-und', + INPUTMETHOD_TRANSLITERATION_PUNJABI: 'pa-t-i0-und', + INPUTMETHOD_TRANSLITERATION_RUSSIAN: 'ru-t-i0-und', + INPUTMETHOD_TRANSLITERATION_SANSKRIT: 'sa-t-i0-und', + INPUTMETHOD_TRANSLITERATION_SERBIAN: 'sr-t-i0-und', + INPUTMETHOD_TRANSLITERATION_SINHALESE: 'si-t-i0-und', + INPUTMETHOD_TRANSLITERATION_SPANISH: 'es-t-i0-und', + INPUTMETHOD_TRANSLITERATION_TAMIL: 'ta-t-i0-und', + INPUTMETHOD_TRANSLITERATION_TELUGU: 'te-t-i0-und', + INPUTMETHOD_TRANSLITERATION_TIGRINYA: 'ti-t-i0-und', + INPUTMETHOD_TRANSLITERATION_TURKISH: 'tr' + '-t-i0-und', + INPUTMETHOD_TRANSLITERATION_UKRAINE: 'uk-t-i0-und', + INPUTMETHOD_TRANSLITERATION_URDU: 'ur-t-i0-und', + INPUTMETHOD_TRANSLITERATION_VIETNAMESE: 'vi-t-i0-und', + INPUTMETHOD_WUBI_CHINESE_SIMPLIFIED: 'zh-t-i0-wubi-1986', + INPUTMETHOD_ZHUYIN_CHINESE_TRADITIONAL: 'zh-hant-t-i0-und', + + // For keyboard + KEYBOARD_ALBANIAN: 'sq-t-k0-und', + KEYBOARD_ARABIC: 'ar-t-k0-und', + KEYBOARD_ARMENIAN_EASTERN: 'hy-hyr-t-k0-und', + KEYBOARD_ARMENIAN_WESTERN: 'hy-hyt-t-k0-und', + KEYBOARD_BASQUE: 'eu-t-k0-und', + KEYBOARD_BELARUSIAN: 'be-t-k0-und', + KEYBOARD_BENGALI_INSCRIPT: 'bn-t-k0-und', + KEYBOARD_BENGALI_PHONETIC: 'bn-t-und-latn-k0-und', + KEYBOARD_BOSNIAN: 'bs-t-k0-und', + KEYBOARD_BRAZILIAN_PORTUGUESE: 'pt-br-t-k0-und', + KEYBOARD_BULGARIAN: 'bg-t-k0-und', + KEYBOARD_BULGARIAN_PHONETIC: 'bg-t-k0-qwerty', + KEYBOARD_CATALAN: 'ca-t-k0-und', + KEYBOARD_CHEROKEE: 'chr-t-k0-und', + KEYBOARD_CHEROKEE_PHONETIC: 'chr-t-und-latn-k0-und', + KEYBOARD_CROATIAN: 'hr-t-k0-und', + KEYBOARD_CZECH: 'cs-t-k0-und', + KEYBOARD_CZECH_QWERTZ: 'cs-t-k0-qwertz', + KEYBOARD_DANISH: 'da-t-k0-und', + KEYBOARD_DARI: 'prs-t-k0-und', + KEYBOARD_DEVANAGARI_PHONETIC: 'hi-t-k0-qwerty', + KEYBOARD_DUTCH: 'nl-t-k0-und', + KEYBOARD_DUTCH_INTL: 'nl-t-k0-intl', + KEYBOARD_DZONGKHA: 'dz-t-k0-und', + KEYBOARD_ENGLISH: 'en-t-k0-und', + KEYBOARD_ENGLISH_DVORAK: 'en-t-k0-dvorak', + KEYBOARD_ESTONIAN: 'et-t-k0-und', + KEYBOARD_ETHIOPIC: 'und-ethi-t-k0-und', + KEYBOARD_TIGRINYA_ETHIOPIC: 'ti-ethi-t-k0-und', + KEYBOARD_FINNISH: 'fi-t-k0-und', + KEYBOARD_FRENCH: 'fr-t-k0-und', + KEYBOARD_FRENCH_INTL: 'fr-t-k0-intl', + KEYBOARD_GALICIAN: 'gl-t-k0-und', + KEYBOARD_GEORGIAN_QWERTY: 'ka-t-k0-und', + KEYBOARD_GEORGIAN_TYPEWRITER: 'ka-t-k0-legacy', + KEYBOARD_GERMAN: 'de-t-k0-und', + KEYBOARD_GERMAN_INTL: 'de-t-k0-intl', + KEYBOARD_GREEK: 'el-t-k0-und', + KEYBOARD_GUJARATI_INSCRIPT: 'gu-t-k0-und', + KEYBOARD_GUJARATI_PHONETIC: 'gu-t-und-latn-k0-qwerty', + KEYBOARD_GURMUKHI_INSCRIPT: 'pa-guru-t-k0-und', + KEYBOARD_GURMUKHI_PHONETIC: 'pa-guru-t-und-latn-k0-und', + KEYBOARD_HAITIAN: 'ht-t-k0-und', + KEYBOARD_HEBREW: 'he-t-k0-und', + KEYBOARD_HINDI: 'hi-t-k0-und', + KEYBOARD_HUNGARIAN_101: 'hu-t-k0-101key', + KEYBOARD_ICELANDIC: 'is-t-k0-und', + KEYBOARD_INDONESIAN: 'id-t-k0-und', + KEYBOARD_INUKTITUT_NUNAVIK: 'iu-t-k0-nunavik', + KEYBOARD_INUKTITUT_NUNAVUT: 'iu-t-k0-nunavut', + KEYBOARD_IRISH: 'ga-t-k0-und', + KEYBOARD_ITALIAN: 'it-t-k0-und', + KEYBOARD_ITALIAN_INTL: 'it-t-k0-intl', + KEYBOARD_JAVANESE: 'jw-t-k0-und', + KEYBOARD_KANNADA_INSCRIPT: 'kn-t-k0-und', + KEYBOARD_KANNADA_PHONETIC: 'kn-t-und-latn-k0-und', + KEYBOARD_KAZAKH: 'kk-t-k0-und', + KEYBOARD_KHMER: 'km-t-k0-und', + KEYBOARD_KOREAN: 'ko-t-k0-und', + KEYBOARD_KYRGYZ: 'ky-cyrl-t-k0-und', + KEYBOARD_LAO: 'lo-t-k0-und', + KEYBOARD_LATVIAN: 'lv-t-k0-und', + KEYBOARD_LITHUANIAN: 'lt-t-k0-und', + KEYBOARD_MACEDONIAN: 'mk-t-k0-und', + KEYBOARD_MALAY: 'ms-t-k0-und', + KEYBOARD_MALAYALAM_INSCRIPT: 'ml-t-k0-und', + KEYBOARD_MALAYALAM_PHONETIC: 'ml-t-und-latn-k0-und', + KEYBOARD_MALTESE: 'mt-t-k0-und', + KEYBOARD_MAORI: 'mi-t-k0-und', + KEYBOARD_MARATHI: 'mr-t-k0-und', + KEYBOARD_MONGOLIAN_CYRILLIC: 'mn-cyrl-t-k0-und', + KEYBOARD_MONTENEGRIN: 'srp-t-k0-und', + KEYBOARD_MYANMAR: 'my-t-k0-und', + KEYBOARD_MYANMAR_MYANSAN: 'my-t-k0-myansan', + KEYBOARD_NAVAJO: 'nv-t-k0-und', + KEYBOARD_NAVAJO_STANDARD: 'nv-t-k0-std', + KEYBOARD_NEPALI_INSCRIPT: 'ne-t-k0-und', + KEYBOARD_NEPALI_PHONETIC: 'ne-t-und-latn-k0-und', + KEYBOARD_NORWEGIAN: 'no-t-k0-und', + KEYBOARD_ORIYA_INSCRIPT: 'or-t-k0-und', + KEYBOARD_ORIYA_PHONETIC: 'or-t-und-latn-k0-und', + KEYBOARD_PAN_AFRICA_LATIN: 'latn-002-t-k0-und', + KEYBOARD_PASHTO: 'ps-t-k0-und', + KEYBOARD_PERSIAN: 'fa-t-k0-und', + KEYBOARD_POLISH: 'pl-t-k0-und', + KEYBOARD_PORTUGUESE: 'pt-pt-t-k0-und', + KEYBOARD_PORTUGUESE_BRAZIL_INTL: 'pt-br-t-k0-intl', + KEYBOARD_PORTUGUESE_PORTUGAL_INTL: 'pt-pt-t-k0-intl', + KEYBOARD_ROMANI: 'rom-t-k0-und', + KEYBOARD_ROMANIAN: 'ro-t-k0-und', + KEYBOARD_ROMANIAN_SR13392_PRIMARY: 'ro-t-k0-legacy', + KEYBOARD_ROMANIAN_SR13392_SECONDARY: 'ro-t-k0-extended', + KEYBOARD_RUSSIAN: 'ru-t-k0-und', + KEYBOARD_RUSSIAN_PHONETIC: 'ru-t-k0-qwerty', + KEYBOARD_SANSKRIT_PHONETIC: 'sa-t-und-latn-k0-und', + KEYBOARD_SERBIAN_CYRILLIC: 'sr-cyrl-t-k0-und', + KEYBOARD_SERBIAN_LATIN: 'sr-latn-t-k0-und', + KEYBOARD_SINHALA: 'si-t-k0-und', + KEYBOARD_SLOVAK: 'sk-t-k0-und', + KEYBOARD_SLOVAK_QWERTY: 'sk-t-k0-qwerty', + KEYBOARD_SLOVENIAN: 'sl-t-k0-und', + KEYBOARD_SORANI_KURDISH_AR: 'ckb-t-k0-ar', // Arabic-based + KEYBOARD_SORANI_KURDISH_EN: 'ckb-t-k0-en', // English-based + KEYBOARD_SOUTHERN_UZBEK: 'uzs-t-k0-und', + KEYBOARD_SPANISH: 'es-t-k0-und', + KEYBOARD_SPANISH_INTL: 'es-t-k0-intl', + KEYBOARD_SWAHILI: 'sw-t-k0-und', + KEYBOARD_SWEDISH: 'sv-t-k0-und', + KEYBOARD_SWISS_GERMAN: 'de-ch-t-k0-und', + KEYBOARD_TAGALOG: 'tl-t-k0-und', + KEYBOARD_TAMIL_99: 'ta-t-k0-ta99', + KEYBOARD_TAMIL_INSCRIPT: 'ta-t-k0-und', + KEYBOARD_TAMIL_ITRANS: 'ta-t-k0-itrans', + KEYBOARD_TAMIL_PHONETIC: 'ta-t-und-latn-k0-und', + KEYBOARD_TAMIL_TYPEWRITER: 'ta-t-k0-typewriter', + KEYBOARD_TATAR: 'tt-t-k0-und', + KEYBOARD_TELUGU_INSCRIPT: 'te-t-k0-und', + KEYBOARD_TELUGU_PHONETIC: 'te-t-und-latn-k0-und', + KEYBOARD_THAI: 'th-t-k0-und', + KEYBOARD_THAI_PATTAJOTI: 'th-t-k0-pattajoti', + KEYBOARD_THAI_TIS: 'th-t-k0-tis', + KEYBOARD_TIGRINYA: 'ti-t-k0-und', + // Gmail doesn't allow any string prefix is 'tr'. + // String with 'tr' prefix will obfuscated in css compiling pharse. + KEYBOARD_TURKISH_F: 'tr' + '-t-k0-legacy', + KEYBOARD_TURKISH_Q: 'tr' + '-t-k0-und', + KEYBOARD_UIGHUR: 'ug-t-k0-und', + KEYBOARD_UKRAINIAN_101: 'uk-t-k0-101key', + KEYBOARD_URDU: 'ur-t-k0-und', + KEYBOARD_US_INTERNATIONAL: 'en-us-t-k0-intl', + KEYBOARD_UZBEK_CYRILLIC_PHONETIC: 'uz-cyrl-t-k0-und', + KEYBOARD_UZBEK_CYRILLIC_TYPEWRITTER: 'uz-cyrl-t-k0-legacy', + KEYBOARD_UZBEK_LATIN: 'uz-latn-t-k0-und', + KEYBOARD_VIETNAMESE_TCVN: 'vi-t-k0-und', + KEYBOARD_VIETNAMESE_TELEX: 'vi-t-k0-legacy', + KEYBOARD_VIETNAMESE_VIQR: 'vi-t-k0-viqr', + KEYBOARD_VIETNAMESE_VNI: 'vi-t-k0-vni', + KEYBOARD_WELSH: 'cy-t-k0-und', + KEYBOARD_YIDDISH: 'yi-t-k0-und', + + // Handwriting codes + HANDWRIT_AFRIKAANS: 'af-t-i0-handwrit', + HANDWRIT_ALBANIAN: 'sq-t-i0-handwrit', + HANDWRIT_ARABIC: 'ar-t-i0-handwrit', + HANDWRIT_BASQUE: 'eu-t-i0-handwrit', + HANDWRIT_BELARUSIAN: 'be-t-i0-handwrit', + HANDWRIT_BOSNIAN: 'bs-t-i0-handwrit', + HANDWRIT_BULGARIAN: 'bg-t-i0-handwrit', + HANDWRIT_CANTONESE: 'zh-yue-t-i0-handwrit', + HANDWRIT_CATALAN: 'ca-t-i0-handwrit', + HANDWRIT_CEBUANO: 'ceb-t-i0-handwrit', + HANDWRIT_CHINESE: 'zh-t-i0-handwrit', + HANDWRIT_CHINESE_SIMPLIFIED: 'zh-hans-t-i0-handwrit', + HANDWRIT_CHINESE_TRADITIONAL: 'zh-hant-t-i0-handwrit', + HANDWRIT_CROATIAN: 'hr-t-i0-handwrit', + HANDWRIT_CZECH: 'cs-t-i0-handwrit', + HANDWRIT_DANISH: 'da-t-i0-handwrit', + HANDWRIT_DUTCH: 'nl-t-i0-handwrit', + HANDWRIT_ENGLISH: 'en-t-i0-handwrit', + HANDWRIT_ESPERANTO: 'eo-t-i0-handwrit', + HANDWRIT_ESTONIAN: 'et-t-i0-handwrit', + HANDWRIT_FILIPINO: 'fil-t-i0-handwrit', + HANDWRIT_FINNISH: 'fi-t-i0-handwrit', + HANDWRIT_FRENCH: 'fr-t-i0-handwrit', + HANDWRIT_GALICIAN: 'gl-t-i0-handwrit', + HANDWRIT_GERMAN: 'de-t-i0-handwrit', + HANDWRIT_GREEK: 'el-t-i0-handwrit', + HANDWRIT_GUJARATI: 'gu-t-i0-handwrit', + HANDWRIT_HAITIAN: 'ht-t-i0-handwrit', + HANDWRIT_HEBREW: 'he-t-i0-handwrit', + HANDWRIT_HINDI: 'hi-t-i0-handwrit', + HANDWRIT_HMONG: 'hmn-t-i0-handwrit', + HANDWRIT_HUNGARIAN: 'hu-t-i0-handwrit', + HANDWRIT_ICELANDIC: 'is-t-i0-handwrit', + HANDWRIT_INDONESIAN: 'id-t-i0-handwrit', + HANDWRIT_IRISH: 'ga-t-i0-handwrit', + HANDWRIT_ITALIAN: 'it-t-i0-handwrit', + HANDWRIT_JAPANESE: 'ja-t-i0-handwrit', + HANDWRIT_JAVANESE: 'jv-t-i0-handwrit', + HANDWRIT_KANNADA: 'kn-t-i0-handwrit', + HANDWRIT_KHMER: 'km-t-i0-handwrit', + HANDWRIT_KOREAN: 'ko-t-i0-handwrit', + HANDWRIT_KURDISH: 'ku-t-i0-handwrit', + HANDWRIT_KYRGYZ: 'ky-t-i0-handwrit', + HANDWRIT_LAO: 'lo-t-i0-handwrit', + HANDWRIT_LATIN: 'la-t-i0-handwrit', + HANDWRIT_LATVIAN: 'lv-t-i0-handwrit', + HANDWRIT_LITHUANIAN: 'lt-t-i0-handwrit', + HANDWRIT_MACEDONIAN: 'mk-t-i0-handwrit', + HANDWRIT_MALAGASY: 'mg-t-i0-handwrit', + HANDWRIT_MALAY: 'ms-t-i0-handwrit', + HANDWRIT_MALTESE: 'mt-t-i0-handwrit', + HANDWRIT_MAORI: 'mi-t-i0-handwrit', + HANDWRIT_MARATHI: 'mr-t-i0-handwrit', + HANDWRIT_MONGOLIAN: 'mn-t-i0-handwrit', + HANDWRIT_NORWEGIAN: 'no-t-i0-handwrit', + HANDWRIT_NORWEGIAN_BOKMAL: 'nb-t-i0-handwrit', + HANDWRIT_NORWEGIAN_NYNORSK: 'nn-t-i0-handwrit', + HANDWRIT_NYANJA: 'ny-t-i0-handwrit', + HANDWRIT_ORIYA: 'or-t-i0-handwrit', + HANDWRIT_PERSIAN: 'fa-t-i0-handwrit', + HANDWRIT_POLISH: 'pl-t-i0-handwrit', + HANDWRIT_PORTUGUESE: 'pt-t-i0-handwrit', + HANDWRIT_PORTUGUESE_BRAZIL: 'pt-br-t-i0-handwrit', + HANDWRIT_PORTUGUESE_PORTUGAL: 'pt-pt-t-i0-handwrit', + HANDWRIT_PUNJABI: 'pa-t-i0-handwrit', + HANDWRIT_ROMANIAN: 'ro-t-i0-handwrit', + HANDWRIT_RUSSIAN: 'ru-t-i0-handwrit', + HANDWRIT_SERBIAN: 'sr-t-i0-handwrit', + HANDWRIT_SLOVAK: 'sk-t-i0-handwrit', + HANDWRIT_SLOVENIAN: 'sl-t-i0-handwrit', + HANDWRIT_SOMALI: 'so-t-i0-handwrit', + HANDWRIT_SPANISH: 'es-t-i0-handwrit', + HANDWRIT_SUNDANESE: 'su-t-i0-handwrit', + HANDWRIT_SWAHILI: 'sw-t-i0-handwrit', + HANDWRIT_SWEDISH: 'sv-t-i0-handwrit', + HANDWRIT_TAMIL: 'ta-t-i0-handwrit', + HANDWRIT_TELUGU: 'te-t-i0-handwrit', + HANDWRIT_THAI: 'th-t-i0-handwrit', + HANDWRIT_TURKISH: 'tr' + '-t-i0-handwrit', + HANDWRIT_UKRAINIAN: 'uk-t-i0-handwrit', + HANDWRIT_VIETNAMESE: 'vi-t-i0-handwrit', + HANDWRIT_WELSH: 'cy-t-i0-handwrit', + HANDWRIT_XHOSA: 'xh-t-i0-handwrit', + HANDWRIT_ZULU: 'zu-t-i0-handwrit', + + // Voice + VOICE_ENGLISH: 'en-t-i0-voice', + + // XKB + XKB_AM_PHONETIC_ARM: 'xkb:am:phonetic:arm', + XKB_BE_FRA: 'xkb:be::fra', + XKB_BE_GER: 'xkb:be::ger', + XKB_BE_NLD: 'xkb:be::nld', + XKB_BG_BUL: 'xkb:bg::bul', + XKB_BG_PHONETIC_BUL: 'xkb:bg:phonetic:bul', + XKB_BR_POR: 'xkb:br::por', + XKB_BY_BEL: 'xkb:by::bel', + XKB_CA_FRA: 'xkb:ca::fra', + XKB_CA_ENG_ENG: 'xkb:ca:eng:eng', + XKB_CA_MULTIX_FRA: 'xkb:ca:multix:fra', + XKB_CH_GER: 'xkb:ch::ger', + XKB_CH_FR_FRA: 'xkb:ch:fr:fra', + XKB_CZ_CZE: 'xkb:cz::cze', + XKB_CZ_QWERTY_CZE: 'xkb:cz:qwerty:cze', + XKB_DE_GER: 'xkb:de::ger', + XKB_DE_NEO_GER: 'xkb:de:neo:ger', + XKB_DK_DAN: 'xkb:dk::dan', + XKB_EE_EST: 'xkb:ee::est', + XKB_ES_SPA: 'xkb:es::spa', + XKB_ES_CAT_CAT: 'xkb:es:cat:cat', + XKB_FI_FIN: 'xkb:fi::fin', + XKB_FR_FRA: 'xkb:fr::fra', + XKB_GB_DVORAK_ENG: 'xkb:gb:dvorak:eng', + XKB_GB_EXTD_ENG: 'xkb:gb:extd:eng', + XKB_GE_GEO: 'xkb:ge::geo', + XKB_GR_GRE: 'xkb:gr::gre', + XKB_HR_SCR: 'xkb:hr::scr', + XKB_HU_HUN: 'xkb:hu::hun', + XKB_IE_GA: 'xkb:ie::ga', + XKB_IL_HEB: 'xkb:il::heb', + XKB_IS_ICE: 'xkb:is::ice', + XKB_IT_ITA: 'xkb:it::ita', + XKB_JP_JPN: 'xkb:jp::jpn', + XKB_LATAM_SPA: 'xkb:latam::spa', + XKB_LT_LIT: 'xkb:lt::lit', + XKB_LV_APOSTROPHE_LAV: 'xkb:lv:apostrophe:lav', + XKB_MN_MON: 'xkb:mn::mon', + XKB_NO_NOB: 'xkb:no::nob', + XKB_PL_POL: 'xkb:pl::pol', + XKB_PT_POR: 'xkb:pt::por', + XKB_RO_RUM: 'xkb:ro::rum', + XKB_RS_SRP: 'xkb:rs::srp', + XKB_RU_RUS: 'xkb:ru::rus', + XKB_RU_PHONETIC_RUS: 'xkb:ru:phonetic:rus', + XKB_SE_SWE: 'xkb:se::swe', + XKB_SI_SLV: 'xkb:si::slv', + XKB_SK_SLO: 'xkb:sk::slo', + XKB_TR_TUR: 'xkb:tr::tur', + XKB_UA_UKR: 'xkb:ua::ukr', + XKB_US_ENG: 'xkb:us::eng', + XKB_US_FIL: 'xkb:us::fil', + XKB_US_IND: 'xkb:us::ind', + XKB_US_MSA: 'xkb:us::msa', + XKB_US_ALTGR_INTL_ENG: 'xkb:us:altgr-intl:eng', + XKB_US_COLEMAK_ENG: 'xkb:us:colemak:eng', + XKB_US_DVORAK_ENG: 'xkb:us:dvorak:eng', + XKB_US_INTL_ENG: 'xkb:us:intl:eng', + XKB_US_INTL_NLD: 'xkb:us:intl:nld', + XKB_US_INTL_POR: 'xkb:us:intl:por' +}; diff --git a/third_party/google_input_tools/src/chrome/os/inputview/keyboardcontainer.js b/third_party/google_input_tools/src/chrome/os/inputview/keyboardcontainer.js new file mode 100644 index 0000000..824af55 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/keyboardcontainer.js @@ -0,0 +1,311 @@ +// 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.KeyboardContainer'); + +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('goog.ui.Container'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.content.AltDataView'); +goog.require('i18n.input.chrome.inputview.elements.content.CandidateView'); +goog.require('i18n.input.chrome.inputview.elements.content.EmojiView'); +goog.require('i18n.input.chrome.inputview.elements.content.ExpandedCandidateView'); +goog.require('i18n.input.chrome.inputview.elements.content.HandwritingView'); +goog.require('i18n.input.chrome.inputview.elements.content.KeysetView'); +goog.require('i18n.input.chrome.inputview.elements.content.MenuView'); + + + +goog.scope(function() { +var Css = i18n.input.chrome.inputview.Css; +var EmojiView = i18n.input.chrome.inputview.elements.content.EmojiView; +var HandwritingView = i18n.input.chrome.inputview.elements.content. + HandwritingView; +var KeysetView = i18n.input.chrome.inputview.elements.content.KeysetView; +var SpecNodeName = i18n.input.chrome.inputview.SpecNodeName; +var content = i18n.input.chrome.inputview.elements.content; + + + +/** + * The keyboard container. + * + * @param {i18n.input.chrome.inputview.Adapter=} opt_adapter . + * @constructor + * @extends {goog.ui.Container} + */ +i18n.input.chrome.inputview.KeyboardContainer = function(opt_adapter) { + goog.base(this); + + /** @type {!content.CandidateView} */ + this.candidateView = new content.CandidateView('candidateView', this); + + /** @type {!content.AltDataView} */ + this.altDataView = new content.AltDataView(this); + + /** @type {!content.MenuView} */ + this.menuView = new content.MenuView(this); + + /** @type {!content.ExpandedCandidateView} */ + this.expandedCandidateView = new content.ExpandedCandidateView(this); + + /** + * The map of the KeysetViews. + * Key: keyboard code. + * Value: The view object. + * + * @type {!Object.<string, !KeysetView>} + */ + this.keysetViewMap = {}; + + /** + * The bus channel to communicate with background. + * + * @private {i18n.input.chrome.inputview.Adapter} + */ + this.adapter_ = opt_adapter || null; +}; +goog.inherits(i18n.input.chrome.inputview.KeyboardContainer, + goog.ui.Container); +var KeyboardContainer = i18n.input.chrome.inputview.KeyboardContainer; + + +/** @type {!KeysetView} */ +KeyboardContainer.prototype.currentKeysetView; + + +/** + * The padding bottom of the whole keyboard. + * + * @private {number} + */ +KeyboardContainer.PADDING_BOTTOM_ = 7; + + +/** + * The padding value of handwriting panel. + * + * @type {number} + * @private + */ +KeyboardContainer.HANDWRITING_PADDING_ = 22; + + +/** + * The padding of emoji keyset. + * + * @type {number} + * @private + */ +KeyboardContainer.EMOJI_PADDING_ = 22; + + +/** + * An div to wrapper candidate view and keyboard set view. + * + * @private {Element} + */ +KeyboardContainer.prototype.wrapperDiv_ = null; + + +/** @override */ +KeyboardContainer.prototype.createDom = function() { + goog.base(this, 'createDom'); + + var elem = this.getElement(); + this.wrapperDiv_ = this.getDomHelper().createDom( + goog.dom.TagName.DIV, Css.WRAPPER); + this.candidateView.render(this.wrapperDiv_); + this.getDomHelper().appendChild(elem, this.wrapperDiv_); + this.altDataView.render(); + this.menuView.render(); + this.expandedCandidateView.render(this.wrapperDiv_); + this.expandedCandidateView.setVisible(false); + goog.dom.classlist.add(elem, Css.CONTAINER); +}; + + +/** @override */ +KeyboardContainer.prototype.enterDocument = function() { + goog.base(this, 'enterDocument'); + + this.setFocusable(false); + this.setFocusableChildrenAllowed(false); +}; + + +/** + * Updates the whole keyboard. + */ +KeyboardContainer.prototype.update = function() { + this.currentKeysetView && this.currentKeysetView.update(); +}; + + +/** + * Adds a keyset view. + * + * @param {!Object} keysetData . + * @param {!Object} layoutData . + * @param {string} keyset . + * @param {string} languageCode . + * @param {!i18n.input.chrome.inputview.Model} model . + * @param {string} inputToolName . + * @param {!Object.<string, boolean>} conditions . + */ +KeyboardContainer.prototype.addKeysetView = function(keysetData, layoutData, + keyset, languageCode, model, inputToolName, conditions) { + var view; + if (keyset == 'emoji') { + view = new EmojiView(keysetData, layoutData, keyset, languageCode, model, + inputToolName, this, this.adapter_); + } else if (keyset == 'hwt') { + view = new HandwritingView(keysetData, layoutData, keyset, languageCode, + model, inputToolName, this, this.adapter_); + } else { + view = new KeysetView(keysetData, layoutData, keyset, languageCode, model, + inputToolName, this, this.adapter_); + } + view.render(this.wrapperDiv_); + view.applyConditions(conditions); + view.setVisible(false); + this.keysetViewMap[keyset] = view; +}; + + +/** + * Switches to a keyset. + * + * @param {string} keyset . + * @param {string} title . + * @param {boolean} isPasswordBox . + * @param {boolean} isA11yMode . + * @param {string} rawKeyset The raw keyset id will switch to. + * @param {string} lastRawkeyset . + * @param {string} languageCode . + * @return {boolean} True if switched successfully. + */ +KeyboardContainer.prototype.switchToKeyset = function(keyset, title, + isPasswordBox, isA11yMode, rawKeyset, lastRawkeyset, languageCode) { + if (!this.keysetViewMap[keyset]) { + return false; + } + + for (var name in this.keysetViewMap) { + var view = this.keysetViewMap[name]; + if (name == keyset) { + this.candidateView.setVisible(!view.disableCandidateView); + view.setVisible(true); + view.update(); + if (view.spaceKey) { + view.spaceKey.updateTitle(title, !isPasswordBox); + } + if (isA11yMode) { + goog.dom.classlist.add(this.getElement(), Css.A11Y); + } + // If current raw keyset is changed, record it. + if (lastRawkeyset != rawKeyset) { + view.fromKeyset = lastRawkeyset; + } + if (view instanceof HandwritingView) { + view.setLanguagecode(languageCode); + } + this.currentKeysetView = view; + this.candidateView.updateByKeyset(rawKeyset, isPasswordBox, + goog.i18n.bidi.isRtlLanguage(languageCode)); + } else { + view.setVisible(false); + } + } + return true; +}; + + +/** + * Resizes the whole keyboard. + * + * @param {number} width . + * @param {number} height . + * @param {number} widthPercent . + * @param {number} candidateViewHeight . + */ +KeyboardContainer.prototype.resize = function(width, height, widthPercent, + candidateViewHeight) { + if (!this.currentKeysetView) { + return; + } + var elem = this.getElement(); + + var h; + if (this.currentKeysetView.isHandwriting()) { + h = height - KeyboardContainer.HANDWRITING_PADDING_; + elem.style.paddingBottom = ''; + } else { + h = height - KeyboardContainer.PADDING_BOTTOM_; + elem.style.paddingBottom = KeyboardContainer.PADDING_BOTTOM_ + 'px'; + } + + var padding = Math.round((width - width * widthPercent) / 2); + elem.style.paddingLeft = elem.style.paddingRight = padding + 'px'; + + var w = width - 2 * padding; + h = this.currentKeysetView.disableCandidateView ? + h - KeyboardContainer.EMOJI_PADDING_ : h - candidateViewHeight; + + this.candidateView.setWidthInWeight( + this.currentKeysetView.getWidthInWeight()); + this.candidateView.resize(w, candidateViewHeight); + this.currentKeysetView.resize(w, h); + this.expandedCandidateView.resize(w, h); + this.altDataView.resize(screen.width, height); + this.menuView.resize(screen.width, height); +}; + + +/** @override */ +KeyboardContainer.prototype.disposeInternal = function() { + goog.dispose(this.candidateView); + goog.dispose(this.altDataView); + goog.dispose(this.menuView); + for (var key in this.keysetViewMap) { + goog.dispose(this.keysetViewMap[key]); + } + + goog.base(this, 'disposeInternal'); +}; + + +/** + * Whether there are strokes on canvas. + * + * @return {boolean} Whether there are strokes on canvas. + */ +KeyboardContainer.prototype.hasStrokesOnCanvas = function() { + if (this.currentKeysetView) { + return this.currentKeysetView.hasStrokesOnCanvas(); + } else { + return false; + } +}; + + +/** + * Cleans the stokes. + */ +KeyboardContainer.prototype.cleanStroke = function() { + if (this.currentKeysetView) { + this.currentKeysetView.cleanStroke(); + } +}; +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/layouts/compactspacerow.js b/third_party/google_input_tools/src/chrome/os/inputview/layouts/compactspacerow.js new file mode 100644 index 0000000..4499728 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/layouts/compactspacerow.js @@ -0,0 +1,72 @@ +// 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.layouts.CompactSpaceRow'); + +goog.require('i18n.input.chrome.inputview.ConditionName'); +goog.require('i18n.input.chrome.inputview.layouts.util'); + + +/** + * Creates the compact keyboard spaceKey row. + * @param {boolean} isNordic True if the space key row is generated for compact + * nordic layout. + * @return {!Object} The compact spaceKey row. + */ +i18n.input.chrome.inputview.layouts.CompactSpaceRow.create = function( + isNordic) { + var digitSwitcher = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.1 + }); + var globeOrSymbolKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'condition': i18n.input.chrome.inputview.ConditionName.SHOW_GLOBE_OR_SYMBOL, + 'widthInWeight': 1 + }); + var menuKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'condition': i18n.input.chrome.inputview.ConditionName.SHOW_MENU, + 'widthInWeight': 1 + }); + var slashKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1 + }); + var space; + if (isNordic) { + space = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 5 + }); + } else { + space = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 4 + }); + } + var comma = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1 + }); + var period = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1 + }); + var hide = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.1 + }); + + menuKey['spec']['giveWeightTo'] = space['spec']['id']; + globeOrSymbolKey['spec']['giveWeightTo'] = space['spec']['id']; + + var spaceKeyRow = i18n.input.chrome.inputview.layouts.util. + createLinearLayout({ + 'id': 'spaceKeyrow', + 'children': [digitSwitcher, globeOrSymbolKey, menuKey, slashKey, space, + comma, period, hide] + }); + return spaceKeyRow; +}; diff --git a/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsof101.js b/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsof101.js new file mode 100644 index 0000000..9bce0be --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsof101.js @@ -0,0 +1,90 @@ +// 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.layouts.RowsOf101'); + +goog.require('i18n.input.chrome.inputview.layouts.util'); + + +/** + * Creates the top four rows for 101 keyboard. + * + * @return {!Array.<!Object>} The rows. + */ +i18n.input.chrome.inputview.layouts.RowsOf101.create = function() { + var baseKeySpec = { + 'widthInWeight': 1, + 'heightInWeight': 1 + }; + + // Row1 + var backquoteKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 0.94 + }); + var keySequenceOf12 = i18n.input.chrome.inputview.layouts.util. + createKeySequence(baseKeySpec, 12); + var backspaceKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.42 + }); + var row1 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row1', + 'children': [backquoteKey, keySequenceOf12, backspaceKey] + }); + + // Row2 + var tabKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.38 + }); + keySequenceOf12 = i18n.input.chrome.inputview.layouts.util. + createKeySequence(baseKeySpec, 12); + var backSlashKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 0.98 + }); + var row2 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row2', + 'children': [tabKey, keySequenceOf12, backSlashKey] + }); + + // Row3 + var capslockKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.68 + }); + var keySequenceOf11 = i18n.input.chrome.inputview.layouts.util. + createKeySequence(baseKeySpec, 11); + var enterKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.68 + }); + var row3 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row3', + 'children': [capslockKey, keySequenceOf11, enterKey] + }); + + // Row4 + var shiftLeftKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 2.12 + }); + var keySequenceOf9 = i18n.input.chrome.inputview.layouts.util. + createKeySequence(baseKeySpec, 9); + var slashKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.08 + }); + var shiftRightKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 2.16 + }); + var row4 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row4', + 'children': [shiftLeftKey, keySequenceOf9, slashKey, shiftRightKey] + }); + + return [row1, row2, row3, row4]; +}; diff --git a/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsof102.js b/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsof102.js new file mode 100644 index 0000000..4df3e41 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsof102.js @@ -0,0 +1,108 @@ +// 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.layouts.RowsOf102'); + +goog.require('i18n.input.chrome.inputview.layouts.util'); + + +/** + * Creates the top four rows for 102 keyboard. + * + * @return {!Array.<!Object>} The rows. + */ +i18n.input.chrome.inputview.layouts.RowsOf102.create = function() { + var baseKeySpec = { + 'widthInWeight': 1, + 'heightInWeight': 1 + }; + + // Row1 + var backquoteKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 0.94 + }); + var keySequenceOf12 = i18n.input.chrome.inputview.layouts.util. + createKeySequence(baseKeySpec, 12); + var backspaceKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.42 + }); + var row1 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row1', + 'children': [backquoteKey, keySequenceOf12, backspaceKey] + }); + + + // Row2 and row3 + + // First linear layout at the left of the enter. + var tabKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.38 + }); + keySequenceOf12 = i18n.input.chrome.inputview.layouts.util. + createKeySequence(baseKeySpec, 12); + var row2 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row2', + 'children': [tabKey, keySequenceOf12] + }); + + // Second linear layout at the right of the enter. + var capslockKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.74 + }); + keySequenceOf12 = i18n.input.chrome.inputview.layouts.util.createKeySequence({ + 'widthInWeight': 0.97 + }, 12); + var row3 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row3', + 'children': [capslockKey, keySequenceOf12] + }); + + // Vertical layout contains the two rows at the left of the enter. + var vLayout = i18n.input.chrome.inputview.layouts.util.createVerticalLayout({ + 'id': 'row2-3-left', + 'children': [row2, row3] + }); + + // Vertical layout contains enter key. + var enterKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 0.98, + 'heightInWeight': 2 + }); + var enterLayout = i18n.input.chrome.inputview.layouts.util. + createVerticalLayout({ + 'id': 'row2-3-right', + 'children': [enterKey] + }); + + // Linear layout contains the two vertical layout. + var row2and3 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row2-3', + 'children': [vLayout, enterLayout] + }); + + // Row4 + var shiftLeft = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.2 + }); + var keySequenceOf11 = i18n.input.chrome.inputview.layouts.util. + createKeySequence(baseKeySpec, 11); + var shiftRight = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 2.16 + }); + var row4 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row4', + 'children': [shiftLeft, keySequenceOf11, shiftRight] + }); + + return [row1, row2and3, row4]; +}; diff --git a/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsofcompact.js b/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsofcompact.js new file mode 100644 index 0000000..d7bf557 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsofcompact.js @@ -0,0 +1,179 @@ +// 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.layouts.RowsOfCompact'); +goog.provide('i18n.input.chrome.inputview.layouts.RowsOfCompactAzerty'); +goog.provide('i18n.input.chrome.inputview.layouts.RowsOfCompactNordic'); + +goog.require('i18n.input.chrome.inputview.layouts.util'); + + +/** + * Creates the top three rows for compact qwerty keyboard. + * + * @return {!Array.<!Object>} The rows. + */ +i18n.input.chrome.inputview.layouts.RowsOfCompact.create = function() { + var baseKeySpec = { + 'widthInWeight': 1, + 'heightInWeight': 1 + }; + + // Row1 + var keySequenceOf10 = i18n.input.chrome.inputview.layouts.util. + createKeySequence(baseKeySpec, 10); + var backspaceKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.2 + }); + var row1 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row1', + 'children': [keySequenceOf10, backspaceKey] + }); + + // Row2 + // How to add padding + var leftKeyWithPadding = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.5 + }); + var keySequenceOf8 = i18n.input.chrome.inputview.layouts.util. + createKeySequence(baseKeySpec, 8); + var enterKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.7 + }); + var row2 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row2', + 'children': [leftKeyWithPadding, keySequenceOf8, enterKey] + }); + + // Row3 + var shiftLeftKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.1 + }); + var keySequenceOf9 = i18n.input.chrome.inputview.layouts.util. + createKeySequence(baseKeySpec, 9); + var shiftRightKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.1 + }); + var row3 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row3', + 'children': [shiftLeftKey, keySequenceOf9, shiftRightKey] + }); + + return [row1, row2, row3]; +}; + +/** + * Creates the top three rows for compact azerty keyboard. + * + * @return {!Array.<!Object>} The rows. + */ +i18n.input.chrome.inputview.layouts.RowsOfCompactAzerty.create = function() { + var baseKeySpec = { + 'widthInWeight': 1, + 'heightInWeight': 1 + }; + + // Row1 + var keySequenceOf10 = i18n.input.chrome.inputview.layouts.util. + createKeySequence(baseKeySpec, 10); + var backspaceKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.2 + }); + var row1 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row1', + 'children': [keySequenceOf10, backspaceKey] + }); + + // Row2 + keySequenceOf10 = i18n.input.chrome.inputview.layouts.util. + createKeySequence(baseKeySpec, 10); + var enterKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.2 + }); + var row2 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row2', + 'children': [keySequenceOf10, enterKey] + }); + + // Row3 + var shiftLeftKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.1 + }); + var keySequenceOf9 = i18n.input.chrome.inputview.layouts.util. + createKeySequence(baseKeySpec, 9); + var shiftRightKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.1 + }); + var row3 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row3', + 'children': [shiftLeftKey, keySequenceOf9, shiftRightKey] + }); + + return [row1, row2, row3]; +}; + +/** + * Creates the top three rows for compact nordic keyboard. + * + * @return {!Array.<!Object>} The rows. + */ +i18n.input.chrome.inputview.layouts.RowsOfCompactNordic.create = function() { + var baseKeySpec = { + 'widthInWeight': 1, + 'heightInWeight': 1 + }; + + // Row1 + var keySequenceOf11 = i18n.input.chrome.inputview.layouts.util. + createKeySequence(baseKeySpec, 11); + var backspaceKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.2 + }); + var row1 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row1', + 'children': [keySequenceOf11, backspaceKey] + }); + + // Row2 + keySequenceOf11 = i18n.input.chrome.inputview.layouts.util. + createKeySequence(baseKeySpec, 11); + var enterKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.2 + }); + var row2 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row2', + 'children': [keySequenceOf11, enterKey] + }); + + // Row3 + var shiftLeftKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.1 + }); + var leftKeyWithPadding = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.5 + }); + var keySequenceOf7 = i18n.input.chrome.inputview.layouts.util. + createKeySequence(baseKeySpec, 7); + var rightKeyWithPadding = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.5 + }); + var shiftRightKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.1 + }); + var row3 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row3', + 'children': [shiftLeftKey, leftKeyWithPadding, keySequenceOf7, + rightKeyWithPadding, shiftRightKey] + }); + return [row1, row2, row3]; +}; diff --git a/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsofjp.js b/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsofjp.js new file mode 100644 index 0000000..d254039 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/layouts/rowsofjp.js @@ -0,0 +1,109 @@ +// 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.layouts.RowsOfJP'); + +goog.require('i18n.input.chrome.inputview.layouts.util'); + + + +goog.scope(function() { +var util = i18n.input.chrome.inputview.layouts.util; + + +/** + * Creates the top four rows for Japanese keyboard. + * + * @return {!Array.<!Object>} The rows. + */ +i18n.input.chrome.inputview.layouts.RowsOfJP.create = function() { + var baseKeySpec = { + 'widthInWeight': 1, + 'heightInWeight': 1 + }; + + // Row1 + var keySequenceOf15 = util.createKeySequence(baseKeySpec, 15); + var row1 = util.createLinearLayout({ + 'id': 'row1', + 'children': [keySequenceOf15] + }); + + + // Row2 and row3 + + // First linear layout at the left of the enter. + var tabKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.5 + }); + var keySequenceOf11 = i18n.input.chrome.inputview.layouts.util. + createKeySequence(baseKeySpec, 11); + var slashKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.25 + }); + var row2 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row2', + 'children': [tabKey, keySequenceOf11, slashKey] + }); + + // Second linear layout at the right of the enter. + var capslockKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.75 + }); + var keySequenceOf12 = i18n.input.chrome.inputview.layouts.util. + createKeySequence(baseKeySpec, 12); + var row3 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row3', + 'children': [capslockKey, keySequenceOf12] + }); + + // Vertical layout contains the two rows at the left of the enter. + var vLayout = i18n.input.chrome.inputview.layouts.util.createVerticalLayout({ + 'id': 'row2-3-left', + 'children': [row2, row3] + }); + + // Vertical layout contains enter key. + var enterKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.25, + 'heightInWeight': 2 + }); + var enterLayout = i18n.input.chrome.inputview.layouts.util. + createVerticalLayout({ + 'id': 'row2-3-right', + 'children': [enterKey] + }); + + // Linear layout contains the two vertical layout. + var row2and3 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row2-3', + 'children': [vLayout, enterLayout] + }); + + // Row4 + var shiftLeft = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 2.25 + }); + keySequenceOf11 = i18n.input.chrome.inputview.layouts.util.createKeySequence( + baseKeySpec, 11); + var shiftRight = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.75 + }); + var row4 = i18n.input.chrome.inputview.layouts.util.createLinearLayout({ + 'id': 'row4', + 'children': [shiftLeft, keySequenceOf11, shiftRight] + }); + + return [row1, row2and3, row4]; +}; +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/layouts/spacerow.js b/third_party/google_input_tools/src/chrome/os/inputview/layouts/spacerow.js new file mode 100644 index 0000000..41e4708 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/layouts/spacerow.js @@ -0,0 +1,76 @@ +// 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.layouts.SpaceRow'); + +goog.require('i18n.input.chrome.inputview.ConditionName'); +goog.require('i18n.input.chrome.inputview.layouts.util'); + + +/** + * Creates the spaceKey row. + * + * @return {!Object} The spaceKey row. + */ +i18n.input.chrome.inputview.layouts.SpaceRow.create = function() { + var globeKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'condition': i18n.input.chrome.inputview.ConditionName.SHOW_GLOBE_OR_SYMBOL, + 'widthInWeight': 1 + }); + var menuKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'condition': i18n.input.chrome.inputview.ConditionName.SHOW_MENU, + 'widthInWeight': 1 + }); + var ctrlKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1 + }); + var altKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1 + }); + var spaceKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 4.87 + }); + var enSwitcher = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1, + 'condition': i18n.input.chrome.inputview.ConditionName. + SHOW_EN_SWITCHER_KEY + }); + var altGrKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.25, + 'condition': i18n.input.chrome.inputview.ConditionName. + SHOW_ALTGR + }); + // If globeKey or altGrKey is not shown, give its weight to space key. + globeKey['spec']['giveWeightTo'] = spaceKey['spec']['id']; + menuKey['spec']['giveWeightTo'] = spaceKey['spec']['id']; + altGrKey['spec']['giveWeightTo'] = spaceKey['spec']['id']; + enSwitcher['spec']['giveWeightTo'] = spaceKey['spec']['id']; + + var leftKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.08 + }); + var rightKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.08 + }); + var hideKeyboardKey = i18n.input.chrome.inputview.layouts.util.createKey({ + 'widthInWeight': 1.08 + }); + var spaceKeyRow = i18n.input.chrome.inputview.layouts.util. + createLinearLayout({ + 'id': 'spaceKeyrow', + 'children': [globeKey, menuKey, ctrlKey, altKey, spaceKey, + enSwitcher, altGrKey, leftKey, rightKey, hideKeyboardKey] + }); + return spaceKeyRow; +}; + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/layouts/util.js b/third_party/google_input_tools/src/chrome/os/inputview/layouts/util.js new file mode 100644 index 0000000..00ca944 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/layouts/util.js @@ -0,0 +1,221 @@ +// 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.layouts.util'); +goog.require('i18n.input.chrome.inputview.Css'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); + + + +goog.scope(function() { +var ElementType = i18n.input.chrome.inputview.elements.ElementType; + + +/** + * The id for the key. + * + * @type {number} + * @private + */ +i18n.input.chrome.inputview.layouts.util.keyId_ = 0; + + +/** + * The prefix of the key id, it's overwritten by layout file. + * + * @type {string} + */ +i18n.input.chrome.inputview.layouts.util.keyIdPrefix = ''; + +/** + * Resets id counter for keys in preparation for processing a new layout. + * @param {string} prefix The prefix for the key id. + */ +i18n.input.chrome.inputview.layouts.util.setPrefix = function(prefix) { + i18n.input.chrome.inputview.layouts.util.keyIdPrefix = prefix; + i18n.input.chrome.inputview.layouts.util.keyId_ = 0; +}; + +/** + * Creates a sequence of key with the same specification. + * + * @param {!Object} spec The specification. + * @param {number} num How many keys. + * @return {!Array.<Object>} The keys. + */ +i18n.input.chrome.inputview.layouts.util.createKeySequence = function(spec, + num) { + var sequence = []; + for (var i = 0; i < num; i++) { + sequence.push(i18n.input.chrome.inputview.layouts.util.createKey(spec)); + } + return sequence; +}; + + +/** + * Creates a soft key view. + * + * @param {!Object} spec The specification. + * @param {string=} opt_id The id. + * @return {!Object} The soft key view. + */ +i18n.input.chrome.inputview.layouts.util.createKey = function(spec, opt_id) { + var id = i18n.input.chrome.inputview.layouts.util.keyIdPrefix + + i18n.input.chrome.inputview.layouts.util.keyId_++; + return i18n.input.chrome.inputview.layouts.util.createElem( + ElementType.SOFT_KEY_VIEW, spec, id); +}; + + +/** + * Creates a linear layout. + * + * @param {!Object} spec The specification. + * @param {string=} opt_id The id. + * @return {!Object} The linear layout. + */ +i18n.input.chrome.inputview.layouts.util.createLinearLayout = function(spec, + opt_id) { + return i18n.input.chrome.inputview.layouts.util.createElem( + ElementType.LINEAR_LAYOUT, spec, opt_id, spec['iconCssClass']); +}; + + +/** + * Creates an extended layout. + * + * @param {!Object} spec The specification. + * @param {string=} opt_id The id. + * @return {!Object} The extended layout. + */ +i18n.input.chrome.inputview.layouts.util.createExtendedLayout = function(spec, + opt_id) { + return i18n.input.chrome.inputview.layouts.util.createElem( + ElementType.EXTENDED_LAYOUT, spec, opt_id, spec['iconCssClass']); +}; + + +/** + * Creates a handwriting layout. + * + * @param {!Object} spec The specification. + * @param {string=} opt_id The id. + * @return {!Object} The handwriting layout. + */ +i18n.input.chrome.inputview.layouts.util.createHandwritingLayout = + function(spec, opt_id) { + return i18n.input.chrome.inputview.layouts.util.createElem( + ElementType.HANDWRITING_LAYOUT, spec, opt_id); +}; + + +/** + * Creates a vertical layout. + * + * @param {!Object} spec The specification. + * @param {string=} opt_id The id. + * @return {!Object} The vertical layout. + */ +i18n.input.chrome.inputview.layouts.util.createVerticalLayout = function(spec, + opt_id) { + return i18n.input.chrome.inputview.layouts.util.createElem( + ElementType.VERTICAL_LAYOUT, spec, opt_id); +}; + + +/** + * Creates a layout view. + * + * @param {!Object} spec The specification. + * @param {string=} opt_id The id. + * @return {!Object} The view. + */ +i18n.input.chrome.inputview.layouts.util.createLayoutView = function(spec, + opt_id) { + return i18n.input.chrome.inputview.layouts.util.createElem( + ElementType.LAYOUT_VIEW, spec, opt_id); +}; + + +/** + * Creates a candidate view. + * + * @param {!Object} spec The specification. + * @param {string=} opt_id The id. + * @return {!Object} The view. + */ +i18n.input.chrome.inputview.layouts.util.createCandidateView = function(spec, + opt_id) { + return i18n.input.chrome.inputview.layouts.util.createElem( + ElementType.CANDIDATE_VIEW, spec, opt_id); +}; + + +/** + * Creates a canvas view. + * + * @param {!Object} spec The specification. + * @param {string=} opt_id The id. + * @return {!Object} The view. + */ +i18n.input.chrome.inputview.layouts.util.createCanvasView = function(spec, + opt_id) { + return i18n.input.chrome.inputview.layouts.util.createElem( + ElementType.CANVAS_VIEW, spec, opt_id); +}; + + +/** + * Creates the keyboard. + * + * @param {Object} spec The specification. + * @param {string=} opt_id The id. + * @return {Object} The keyboard. + */ +i18n.input.chrome.inputview.layouts.util.createKeyboard = function(spec, + opt_id) { + return i18n.input.chrome.inputview.layouts.util.createElem( + ElementType.KEYBOARD, spec, opt_id); +}; + + +/** + * Creates an element which could be any type, such as soft key view, layout, + * etc. + * + * @param {!ElementType} type The type. + * @param {Object} spec The specification. + * @param {string=} opt_id The id. + * @param {i18n.input.chrome.inputview.Css=} opt_iconCssClass The Css class. + * @return {!Object} The element. + */ +i18n.input.chrome.inputview.layouts.util.createElem = function(type, spec, + opt_id, opt_iconCssClass) { + var newSpec = {}; + for (var key in spec) { + newSpec[key] = spec[key]; + } + newSpec['type'] = type; + if (opt_id) { + newSpec['id'] = opt_id; + } + if (opt_iconCssClass) { + newSpec['iconCssClass'] = opt_iconCssClass; + } + return { + 'spec': newSpec + }; +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/m17nmodel.js b/third_party/google_input_tools/src/chrome/os/inputview/m17nmodel.js new file mode 100644 index 0000000..3082006 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/m17nmodel.js @@ -0,0 +1,157 @@ +// 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.M17nModel'); + +goog.require('goog.events.EventHandler'); +goog.require('goog.events.EventTarget'); +goog.require('i18n.input.chrome.inputview.SpecNodeName'); +goog.require('i18n.input.chrome.inputview.content.util'); +goog.require('i18n.input.chrome.vk.KeyCode'); +goog.require('i18n.input.chrome.vk.Model'); + + + +/** + * The model to legacy cloud vk configuration. + * + * @constructor + * @extends {goog.events.EventTarget} + */ +i18n.input.chrome.inputview.M17nModel = function() { + goog.base(this); + + /** + * The event handler. + * + * @type {!goog.events.EventHandler} + * @private + */ + this.handler_ = new goog.events.EventHandler(this); + + /** + * The model for cloud vk. + * + * @type {!i18n.input.chrome.vk.Model} + * @private + */ + this.model_ = new i18n.input.chrome.vk.Model(); + this.handler_.listen(this.model_, + i18n.input.chrome.vk.EventType.LAYOUT_LOADED, + this.onLayoutLoaded_); +}; +goog.inherits(i18n.input.chrome.inputview.M17nModel, + goog.events.EventTarget); + + +/** + * The active layout view. + * + * @type {!i18n.input.chrome.vk.ParsedLayout} + * @private + */ +i18n.input.chrome.inputview.M17nModel.prototype.layoutView_; + + +/** + * Loads the configuration. + * + * @param {string} lang The m17n keyboard layout code (with 'm17n:' prefix). + */ +i18n.input.chrome.inputview.M17nModel.prototype.loadConfig = function(lang) { + var m17nMatches = lang.match(/^m17n:(.*)/); + if (m17nMatches && m17nMatches[1]) { + this.model_.loadLayout(m17nMatches[1]); + } +}; + + +/** + * Callback when legacy model is loaded. + * + * @param {!i18n.input.chrome.vk.LayoutEvent} e The event. + * @private + */ +i18n.input.chrome.inputview.M17nModel.prototype.onLayoutLoaded_ = function( + e) { + var layoutView = /** @type {!i18n.input.chrome.vk.ParsedLayout} */ + (e.layoutView); + this.layoutView_ = layoutView; + var is102 = layoutView.view.is102; + var codes = is102 ? i18n.input.chrome.vk.KeyCode.CODES102 : + i18n.input.chrome.vk.KeyCode.CODES101; + var keyCount = is102 ? 48 : 47; + var keyCharacters = []; + for (var i = 0; i < keyCount; i++) { + var characters = this.findCharacters_(layoutView.view.mappings, + codes[i]); + keyCharacters.push(characters); + } + keyCharacters.push(['\u0020', '\u0020']); + var hasAltGrKey = !!layoutView.view.mappings['c'] && + layoutView.view.mappings['c'] != layoutView.view.mappings['']; + var skvPrefix = is102 ? '102kbd-k-' : '101kbd-k-'; + var skPrefix = layoutView.view.id + '-k-'; + var data = i18n.input.chrome.inputview.content.util.createData(keyCharacters, + skvPrefix, is102, hasAltGrKey); + if (data) { + data[i18n.input.chrome.inputview.SpecNodeName.TITLE] = + layoutView.view.title; + data[i18n.input.chrome.inputview.SpecNodeName.ID] = + 'm17n:' + e.layoutCode; + this.dispatchEvent(new i18n.input.chrome.inputview.events. + ConfigLoadedEvent(data)); + } +}; + + +/** + * Finds out the characters for the key. + * + * @param {!Object} mappings The mappings. + * @param {string} code The code. + * @return {!Array.<string>} The characters for the code. + * @private + */ +i18n.input.chrome.inputview.M17nModel.prototype.findCharacters_ = function( + mappings, code) { + var characters = []; + var states = [ + '', + 's', + 'c', + 'sc', + 'l', + 'sl', + 'cl', + 'scl' + ]; + for (var i = 0; i < states.length; i++) { + if (mappings[states[i]] && mappings[states[i]][code]) { + characters[i] = mappings[states[i]][code][1]; + } else if (code == '\u0020') { + characters[i] = '\u0020'; + } else { + characters[i] = ''; + } + } + return characters; +}; + + +/** @override */ +i18n.input.chrome.inputview.M17nModel.prototype.disposeInternal = function() { + goog.dispose(this.handler_); + + goog.base(this, 'disposeInternal'); +}; diff --git a/third_party/google_input_tools/src/chrome/os/inputview/model.js b/third_party/google_input_tools/src/chrome/os/inputview/model.js new file mode 100644 index 0000000..d70d9f5 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/model.js @@ -0,0 +1,166 @@ +// 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.Model'); + +goog.require('goog.array'); +goog.require('goog.events.EventTarget'); +goog.require('goog.net.jsloader'); +goog.require('i18n.input.chrome.inputview.ConditionName'); +goog.require('i18n.input.chrome.inputview.Settings'); +goog.require('i18n.input.chrome.inputview.SpecNodeName'); +goog.require('i18n.input.chrome.inputview.StateManager'); +goog.require('i18n.input.chrome.inputview.events.ConfigLoadedEvent'); +goog.require('i18n.input.chrome.inputview.events.LayoutLoadedEvent'); + +goog.scope(function() { +var SpecNodeName = i18n.input.chrome.inputview.SpecNodeName; + + + +/** + * The model. + * @constructor + * @extends {goog.events.EventTarget} + */ +i18n.input.chrome.inputview.Model = function() { + goog.base(this); + + /** + * The state manager. + * + * @type {!i18n.input.chrome.inputview.StateManager} + */ + this.stateManager = new i18n.input.chrome.inputview.StateManager(); + + /** + * The configuration. + * + * @type {!i18n.input.chrome.inputview.Settings} + */ + this.settings = new i18n.input.chrome.inputview.Settings(); + + /** @private {!Array.<string>} */ + this.loadingResources_ = []; + + goog.exportSymbol('google.ime.chrome.inputview.onLayoutLoaded', + goog.bind(this.onLayoutLoaded_, this)); + goog.exportSymbol('google.ime.chrome.inputview.onConfigLoaded', + goog.bind(this.onConfigLoaded_, this)); +}; +var Model = i18n.input.chrome.inputview.Model; +goog.inherits(Model, goog.events.EventTarget); + + +/** + * The path to the layouts directory. + * + * @type {string} + * @private + */ +Model.LAYOUTS_PATH_ = + '/inputview_layouts/'; + + +/** + * The path to the content directory. + * + * @type {string} + * @private + */ +Model.CONTENT_PATH_ = + '/config/'; + + +/** + * Callback when configuration is loaded. + * + * @param {!Object} data The configuration data. + * @private + */ +Model.prototype.onConfigLoaded_ = function(data) { + goog.array.remove(this.loadingResources_, this.getConfigUrl_( + data[SpecNodeName.ID])); + this.dispatchEvent(new i18n.input.chrome.inputview.events.ConfigLoadedEvent( + data)); +}; + + +/** + * Gets the layout url. + * + * @param {string} layout . + * @private + * @return {string} The url of the layout data. + */ +Model.prototype.getLayoutUrl_ = function(layout) { + return Model.LAYOUTS_PATH_ + layout + '.js'; +}; + + +/** + * Gets the keyset configuration url. + * + * @param {string} keyset . + * @private + * @return {string} . + */ +Model.prototype.getConfigUrl_ = function(keyset) { + // Strips out all the suffixes in the keyboard code. + var configId = keyset.replace(/\..*$/, ''); + return Model.CONTENT_PATH_ + configId + '.js'; +}; + + +/** + * Callback when layout is loaded. + * + * @param {!Object} data The layout data. + * @private + */ +Model.prototype.onLayoutLoaded_ = function(data) { + goog.array.remove(this.loadingResources_, this.getLayoutUrl_(data[ + SpecNodeName.LAYOUT_ID])); + this.dispatchEvent(new i18n.input.chrome.inputview.events.LayoutLoadedEvent( + data)); +}; + + +/** + * Loads a layout. + * + * @param {string} layout The layout name. + */ +Model.prototype.loadLayout = function(layout) { + var url = this.getLayoutUrl_(layout); + if (!goog.array.contains(this.loadingResources_, url)) { + this.loadingResources_.push(url); + goog.net.jsloader.load(url); + } +}; + + +/** + * Loads the configuration for the keyboard code. + * + * @param {string} keyboardCode The keyboard code. + */ +Model.prototype.loadConfig = function(keyboardCode) { + var url = this.getConfigUrl_(keyboardCode); + if (!goog.array.contains(this.loadingResources_, url)) { + this.loadingResources_.push(url); + goog.net.jsloader.load(url); + } +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/pointerconfig.js b/third_party/google_input_tools/src/chrome/os/inputview/pointerconfig.js new file mode 100644 index 0000000..3a512b2 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/pointerconfig.js @@ -0,0 +1,76 @@ +// 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.PointerConfig'); + + +/** + * The configuration of the pointer. + * + * @param {boolean} dblClick . + * @param {boolean} longPressWithPointerUp . + * @param {boolean} longPressWithoutPointerUp . + * @constructor + */ +i18n.input.chrome.inputview.PointerConfig = function(dblClick, + longPressWithPointerUp, longPressWithoutPointerUp) { + /** + * True to enable double click. + * + * @type {boolean} + */ + this.dblClick = dblClick; + + /** + * The delay of the double click. If not set or is 0, the default delay(500ms) + * is used. + * + * @type {number} + */ + this.dblClickDelay = 0; + + /** + * True to enable long press and not cancel the next pointer up. + * + * @type {boolean} + */ + this.longPressWithPointerUp = longPressWithPointerUp; + + /** + * True to enable long press and cancel the next pointer up. + * + * @type {boolean} + */ + this.longPressWithoutPointerUp = longPressWithoutPointerUp; + + /** + * The flicker direction. + * + * @type {number} + */ + this.flickerDirection = 0; + + /** + * The delay of the long press. + * + * @type {number} + */ + this.longPressDelay = 0; + + /** @type {boolean} */ + this.stopEventPropagation = true; + + /** @type {boolean} */ + this.preventDefault = true; +}; + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/readystate.js b/third_party/google_input_tools/src/chrome/os/inputview/readystate.js new file mode 100644 index 0000000..6a633b1 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/readystate.js @@ -0,0 +1,87 @@ +// 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.ReadyState'); + + + +goog.scope(function() { +/** + * The system ready state which mainains a state bit map. + * Inputview controller uses this to determine whether the system is ready to + * render the UI. + * + * @constructor + */ +i18n.input.chrome.inputview.ReadyState = function() { +}; +var ReadyState = i18n.input.chrome.inputview.ReadyState; + + +/** + * The state type. + * + * @enum {number} + */ +ReadyState.StateType = { + IME_LIST_READY: 1, + KEYBOARD_CONFIG_READY: 2, + LAYOUT_READY: 4, + LAYOUT_CONFIG_READY: 8, + M17N_LAYOUT_READY: 16 +}; + + +/** + * The internal ready state bit map. + * + * @private {number} + */ +ReadyState.prototype.state_ = 0; + + +/** + * Gets whether the system is ready. + * + * @return {boolean} Whether the system is ready. + */ +ReadyState.prototype.isAllReady = function() { + return !!(this.state_ & ( + ReadyState.StateType.IME_LIST_READY | + ReadyState.StateType.KEYBOARD_CONFIG_READY | + ReadyState.StateType.LAYOUT_READY | + ReadyState.StateType.LAYOUT_CONFIG_READY | + ReadyState.StateType.M17N_LAYOUT_READY)); +}; + + +/** + * Gets whether a specific state type is ready. + * + * @param {ReadyState.StateType} stateType . + * @return {boolean} Whether is ready. + */ +ReadyState.prototype.isReady = function(stateType) { + return !!(this.state_ & stateType); +}; + + +/** + * Sets state ready for the given state type. + * + * @param {ReadyState.StateType} stateType . + */ +ReadyState.prototype.markStateReady = function(stateType) { + this.state_ |= stateType; +}; +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/settings.js b/third_party/google_input_tools/src/chrome/os/inputview/settings.js new file mode 100644 index 0000000..640ee14 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/settings.js @@ -0,0 +1,96 @@ +// 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.Settings'); + + +goog.scope(function() { + + + +/** + * The settings. + * + * @constructor + */ +i18n.input.chrome.inputview.Settings = function() {}; +var Settings = i18n.input.chrome.inputview.Settings; + + +/** + * True to always render the altgr character in the soft key. + * + * @type {boolean} + */ +Settings.prototype.alwaysRenderAltGrCharacter = false; + + +/** @type {boolean} */ +Settings.prototype.autoSpace = false; + + +/** @type {boolean} */ +Settings.prototype.autoCapital = false; + + +/** @type {boolean} */ +Settings.prototype.autoCorrection = false; + + +/** @type {boolean} */ +Settings.prototype.supportCompact = false; + + +/** @type {boolean} */ +Settings.prototype.enableLongPress = true; + + +/** @type {boolean} */ +Settings.prototype.doubleSpacePeriod = false; + + +/** @type {boolean} */ +Settings.prototype.soundOnKeypress = false; + + +/** + * The flag to control whether candidates naviagation feature is enabled. + * + * @type {boolean} + */ +Settings.prototype.candidatesNavigation = false; + + +/** + * Saves the preferences. + * + * @param {string} preference The name of the preference. + * @param {*} value The preference value. + */ +Settings.prototype.savePreference = function(preference, value) { + window.localStorage.setItem(preference, /** @type {string} */(value)); +}; + + +/** + * Gets the preference value. + * + * @param {string} preference The name of the preference. + * @return {*} The value. + */ +Settings.prototype.getPreference = function(preference) { + return window.localStorage.getItem(preference); +}; + +}); // goog.scope + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/sizespec.js b/third_party/google_input_tools/src/chrome/os/inputview/sizespec.js new file mode 100644 index 0000000..ecb58e9 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/sizespec.js @@ -0,0 +1,70 @@ +// 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.SizeSpec'); + + + +goog.scope(function() { + +var SizeSpec = i18n.input.chrome.inputview.SizeSpec; + + +/** + * The height of the keyboard in a11y mode. + * + * @type {number} + */ +SizeSpec.A11Y_HEIGHT = 280; + + +/** + * The height of the keyboard in non-a11y mode. + * + * @type {number} + */ +SizeSpec.NON_A11Y_HEIGHT = 372; + + +/** @type {number} */ +SizeSpec.A11Y_CANDIDATE_VIEW_HEIGHT = 30; + + +/** @type {number} */ +SizeSpec.NON_A11Y_CANDIDATE_VIEW_HEIGHT = 45; + + +/** + * The width percent of a11y keyboard in horizontal mode or vertical mode. + * + * @enum {number} + */ +SizeSpec.A11Y_WIDTH_PERCENT = { + HORIZONTAL: 0.74, + VERTICAL: 0.88 +}; + + +/** + * The width percent of non-a11y keyboard in horizontal mode or vertical mode. + * + * @enum {number} + */ +SizeSpec.NON_A11Y_WIDTH_PERCENT = { + HORIZONTAL: 0.84, + HORIZONTAL_WIDE_SCREEN: 0.788, + VERTICAL: 0.88 +}; + +}); // goog.scope + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/soundcontroller.js b/third_party/google_input_tools/src/chrome/os/inputview/soundcontroller.js new file mode 100644 index 0000000..19b7024 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/soundcontroller.js @@ -0,0 +1,226 @@ +// 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.SoundController'); + +goog.require('goog.Disposable'); +goog.require('goog.dom'); +goog.require('i18n.input.chrome.inputview.Sounds'); +goog.require('i18n.input.chrome.inputview.elements.ElementType'); + +goog.scope(function() { +var Sounds = i18n.input.chrome.inputview.Sounds; +var ElementType = i18n.input.chrome.inputview.elements.ElementType; +var keyToSoundIdOnKeyUp = {}; +var keyToSoundIdOnKeyRepeat = {}; + + +/** + * Sound controller for the keyboard. + * + * @param {!boolean} enabled Whether sounds is enabled by default. + * @param {?number} opt_volume The default volume for sound tracks. + * @constructor + * @extends {goog.Disposable} + */ +i18n.input.chrome.inputview.SoundController = function(enabled, opt_volume) { + + /** + * Collection of all the sound pools. + * + * @type {!Object.<string, !Object>} + */ + this.sounds_ = {}; + + this.enabled_ = enabled; + + /** + * The default volume for all audio tracks. Tracks with volume 0 will be + * skipped. + * + * @type {number} + */ + this.volume_ = opt_volume || this.DEFAULT_VOLUME; + + if (enabled) + this.initialize(); +}; +goog.inherits(i18n.input.chrome.inputview.SoundController, goog.Disposable); + + +var Controller = i18n.input.chrome.inputview.SoundController; + + +/** + * @define {number} The size of the pool to use for playing audio sounds. + */ +Controller.prototype.POOL_SIZE = 10; + + +/** + * @define {number} The default audio track volume. + */ +Controller.prototype.DEFAULT_VOLUME = 0.6; + + +/** + * Initializes the sound controller. + */ +Controller.prototype.initialize = function() { + for (var sound in Sounds) { + this.addSound_(Sounds[sound]); + } + keyToSoundIdOnKeyUp[ElementType.BACKSPACE_KEY] = Sounds.NONE; + keyToSoundIdOnKeyUp[ElementType.ENTER_KEY] = Sounds.RETURN; + keyToSoundIdOnKeyUp[ElementType.SPACE_KEY] = Sounds.SPACEBAR; + keyToSoundIdOnKeyRepeat[ElementType.BACKSPACE_KEY] = Sounds.DELETE; +}; + + +/** + * Caches the specified sound on the keyboard. + * + * @param {string} soundId The name of the .wav file in the "sounds" + directory. + * @private + */ +Controller.prototype.addSound_ = function(soundId) { + if (soundId == Sounds.NONE || this.sounds_[soundId]) + return; + var pool = []; + // Create sound pool. + for (var i = 0; i < this.POOL_SIZE; i++) { + var audio = goog.dom.createDom('audio', { + preload: 'auto', + id: soundId, + src: 'sounds/' + soundId + '.wav', + volume: this.volume_ + }); + pool.push(audio); + } + this.sounds_[soundId] = pool; +}; + + +/** + * Sets the volume for the specified sound. + * + * @param {string} soundId The id of the sound. + * @param {number} volume The volume to set. + */ +Controller.prototype.setVolume = function(soundId, volume) { + var pool = this.sounds_[soundId]; + if (!pool) { + console.error('Cannot find sound: ' + soundId); + return; + } + // Change volume for all sounds in the pool. + for (var i = 0; i < pool.length; i++) { + pool[i].volume = volume; + } +}; + + +/** + * Enables or disable playing sounds on keypress. + * @param {!boolean} enabled + */ +Controller.prototype.setEnabled = function(enabled) { + this.enabled_ = enabled; + if (this.enabled_) + this.initialize(); +}; + + +/** + * Sets the volume for all sounds on the keyboard. + * + * @param {number} volume The volume of the sounds. + */ +Controller.prototype.setMasterVolume = function(volume) { + this.volume_ = volume; + for (var id in this.sounds_) { + this.setVolume(id, volume); + } +}; + + +/** + * Plays the specified sound. + * + * @param {string} soundId The id of the audio tag. + * @private + */ +Controller.prototype.playSound_ = function(soundId) { + // If master volume is zero, ignore the request. + if (!this.enabled_ || this.volume_ == 0 || soundId == Sounds.NONE) + return; + var pool = this.sounds_[soundId]; + if (!pool) { + console.error('Cannot find sound: ' + soundId); + return; + } + // Search the sound pool for a free resource. + for (var i = 0; i < pool.length; i++) { + if (pool[i].paused) { + pool[i].play(); + return; + } + } +}; + + +/** + * On key up. + * + * @param {ElementType} key The key released. + */ + Controller.prototype.onKeyUp = function(key) { + var sound = keyToSoundIdOnKeyUp[key] || Sounds.STANDARD; + this.playSound_(sound); + }; + + + /** + * On key repeat. + * + * @param {ElementType} key The key that is being repeated. + */ + Controller.prototype.onKeyRepeat = function(key) { + var sound = keyToSoundIdOnKeyRepeat[key] || Sounds.NONE; + this.playSound_(sound); + }; + + + /** @override */ + Controller.prototype.disposeInternal = function() { + for (var soundId in this.sounds_) { + var pool = this.sounds_[soundId]; + for (var i = 0; i < pool.length; i++) { + var tag = pool[i]; + if (tag && tag.loaded) { + tag.pause(); + tag.autoplay = false; + tag.loop = false; + tag.currentTime = 0; + } + } + delete this.sounds_[soundId]; + } + this.sounds_ = {}; + keyToSoundIdOnKeyUp = {}; + keyToSoundIdOnKeyRepeat = {}; + goog.base(this, 'disposeInternal'); + }; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/inputview/sounds.js b/third_party/google_input_tools/src/chrome/os/inputview/sounds.js new file mode 100644 index 0000000..ea47003 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/sounds.js @@ -0,0 +1,29 @@ +// 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.Sounds'); + + +/** + * The available sounds. + * + * @enum {string} + */ +i18n.input.chrome.inputview.Sounds = { + DELETE: 'keypress-delete', + RETURN: 'keypress-return', + SPACEBAR: 'keypress-spacebar', + STANDARD: 'keypress-standard', + NONE: 'none' +}; + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/specnodename.js b/third_party/google_input_tools/src/chrome/os/inputview/specnodename.js new file mode 100644 index 0000000..663e1e7 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/specnodename.js @@ -0,0 +1,70 @@ +// 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.SpecNodeName'); + + +/** + * The node name in the specification. + * + * @enum {string} + */ +i18n.input.chrome.inputview.SpecNodeName = { + ALIGN: 'align', + CHARACTERS: 'characters', + CHILDREN: 'children', + CONDITION: 'condition', + DIRECTION: 'direction', + GIVE_WEIGHT_TO: 'giveWeightTo', + HAS_ALTGR_KEY: 'hasAltGrKey', + HAS_COMPACT_KEYBOARD: 'hasCompactKeyboard', + HEIGHT: 'height', + HEIGHT_IN_WEIGHT: 'heightInWeight', + HEIGHT_PERCENT: 'heightPercent', + HINT_TEXT: 'hintText', + ICON_CSS_CLASS: 'iconCssClass', + ID: 'id', + IS_GREY: 'isGrey', + LAYOUT: 'layout', + LAYOUT_ID: 'layoutID', + HEIGHT_PERCENT_OF_WIDTH: 'heightPercentOfWidth', + MARGIN_LEFT_PERCENT: 'marginLeftPercent', + MARGIN_RIGHT_PERCENT: 'marginRightPercent', + MINIMUM_HEIGHT: 'minimumHeight', + MORE_KEYS: 'moreKeys', + NO_SHIFT: 'noShift', + NUMBER_ROW_WEIGHT: 'numberRowWeight', + KEY_CODE: 'keyCode', + KEY_LIST: 'keyList', + MAPPING: 'mapping', + NAME: 'name', + ON_CONTEXT: 'onContext', + PADDING: 'padding', + RECORD: 'record', + SHOW_MENU_KEY: 'showMenuKey', + SUPPORT_STICKY: 'supportSticky', + SPEC: 'spec', + TEXT: 'text', + TEXT_CSS_CLASS: 'textCssClass', + TITLE: 'title', + TYPE: 'type', + TO_STATE: 'toState', + TO_KEYSET: 'toKeyset', + TO_KEYSET_NAME: 'toKeysetName', + WIDTH: 'width', + WIDTH_IN_WEIGHT: 'widthInWeight', + WIDTH_PERCENT: 'widthPercent', + IS_EMOTICON: 'isEmoticon', + MORE_KEYS_SHIFT_OPERATION: 'moreKeysShiftOperation' +}; + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/statemanager.js b/third_party/google_input_tools/src/chrome/os/inputview/statemanager.js new file mode 100644 index 0000000..a82c6eb --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/statemanager.js @@ -0,0 +1,233 @@ +// 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.StateManager'); + +goog.require('i18n.input.chrome.inputview.Covariance'); + + +/** + * The state for the input view keyboard. + * + * @constructor + */ +i18n.input.chrome.inputview.StateManager = function() { + /** + * The state of the keyboard. + * + * @type {number} + * @private + */ + this.state_ = 0; + + /** + * The sticky state. + * + * @type {number} + * @private + */ + this.sticky_ = 0; + + /** + * Bits to indicate which state key is down. + * + * @type {number} + * @private + */ + this.stateKeyDown_ = 0; + + /** + * Bits to track which state is in chording. + * + * @type {number} + * @private + */ + this.chording_ = 0; + + /** + * Whether the current keyset is in English mode. + * + * @type {boolean} + */ + this.isEnMode = false; + + /** @type {!i18n.input.chrome.inputview.Covariance} */ + this.covariance = new i18n.input.chrome.inputview.Covariance(); +}; + + +/** + * Sets a state to keydown. + * + * @param {!i18n.input.chrome.inputview.StateType} stateType The state type. + * @param {boolean} isKeyDown True if the state key is down. + */ +i18n.input.chrome.inputview.StateManager.prototype.setKeyDown = function( + stateType, isKeyDown) { + if (isKeyDown) { + this.stateKeyDown_ |= stateType; + } else { + this.stateKeyDown_ &= ~stateType; + this.chording_ &= ~stateType; + } +}; + + +/** + * True if the key is down. + * + * @param {!i18n.input.chrome.inputview.StateType} stateType . + * @return {boolean} . + */ +i18n.input.chrome.inputview.StateManager.prototype.isKeyDown = function( + stateType) { + return (this.stateKeyDown_ & stateType) != 0; +}; + + +/** + * Triggers chording and record it for each key-downed state. + */ +i18n.input.chrome.inputview.StateManager.prototype.triggerChording = + function() { + this.chording_ |= this.stateKeyDown_; +}; + + +/** + * True if the state is chording now. + * + * @param {!i18n.input.chrome.inputview.StateType} stateType The state type. + * @return {boolean} True if the state is chording. + */ +i18n.input.chrome.inputview.StateManager.prototype.isChording = function( + stateType) { + return (this.chording_ & stateType) != 0; +}; + + +/** + * Sets a state to be sticky. + * + * @param {!i18n.input.chrome.inputview.StateType} stateType The state type. + * @param {boolean} isSticky True to set it sticky. + */ +i18n.input.chrome.inputview.StateManager.prototype.setSticky = function( + stateType, isSticky) { + if (isSticky) { + this.sticky_ |= stateType; + } else { + this.sticky_ &= ~stateType; + } +}; + + +/** + * Is a state sticky. + * + * @param {!i18n.input.chrome.inputview.StateType} stateType The state + * type. + * @return {boolean} True if it is sticky. + */ +i18n.input.chrome.inputview.StateManager.prototype.isSticky = function( + stateType) { + return (this.sticky_ & stateType) != 0; +}; + + +/** + * Sets a state. + * + * @param {!i18n.input.chrome.inputview.StateType} stateType The state + * type. + * @param {boolean} enable True to enable the state. + */ +i18n.input.chrome.inputview.StateManager.prototype.setState = function( + stateType, enable) { + if (enable) { + this.state_ = this.state_ | stateType; + } else { + this.state_ = this.state_ & ~stateType; + } +}; + + +/** + * Toggle the state. + * + * @param {!i18n.input.chrome.inputview.StateType} stateType The state + * type. + * @param {boolean} isSticky True to set it sticky. + */ +i18n.input.chrome.inputview.StateManager.prototype.toggleState = function( + stateType, isSticky) { + var enable = !this.hasState(stateType); + this.setState(stateType, enable); + isSticky = enable ? isSticky : false; + this.setSticky(stateType, isSticky); +}; + + +/** + * Is the state on. + * + * @param {!i18n.input.chrome.inputview.StateType} stateType The state + * type. + * @return {boolean} True if the state is on. + */ +i18n.input.chrome.inputview.StateManager.prototype.hasState = function( + stateType) { + return (this.state_ & stateType) != 0; +}; + + +/** + * Gets the state. + * + * @return {number} The state. + */ +i18n.input.chrome.inputview.StateManager.prototype.getState = + function() { + return this.state_; +}; + + +/** + * Clears unsticky state. + * + */ +i18n.input.chrome.inputview.StateManager.prototype.clearUnstickyState = + function() { + this.state_ = this.state_ & this.sticky_; +}; + + +/** + * True if there is unsticky state. + * + * @return {boolean} True if there is unsticky state. + */ +i18n.input.chrome.inputview.StateManager.prototype.hasUnStickyState = + function() { + return this.state_ != this.sticky_; +}; + + +/** + * Resets the state. + * + */ +i18n.input.chrome.inputview.StateManager.prototype.reset = function() { + this.state_ = 0; + this.sticky_ = 0; +}; diff --git a/third_party/google_input_tools/src/chrome/os/inputview/statetype.js b/third_party/google_input_tools/src/chrome/os/inputview/statetype.js new file mode 100644 index 0000000..bddf4b9 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/statetype.js @@ -0,0 +1,30 @@ +// 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.StateType'); + + +/** + * The state. + * + * @enum {number} + */ +i18n.input.chrome.inputview.StateType = { + DEFAULT: 0, + SHIFT: 1, + ALTGR: 2, + CAPSLOCK: 4, + CTRL: 8, + ALT: 16 +}; + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/statistics.js b/third_party/google_input_tools/src/chrome/os/inputview/statistics.js new file mode 100644 index 0000000..d869b73 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/statistics.js @@ -0,0 +1,147 @@ +// 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.Statistics'); + + + +/** + * The statistics util class for input view. + * + * @constructor + */ +i18n.input.chrome.inputview.Statistics = function() { +}; +goog.addSingletonGetter(i18n.input.chrome.inputview.Statistics); + + +/** + * The current layout code. + * + * @type {string} + * @private + */ +i18n.input.chrome.inputview.Statistics.prototype.layoutCode_ = ''; + + +/** + * Sets the current layout code, which will be a part of the metrics name. + * + * @param {string} layoutCode . + */ +i18n.input.chrome.inputview.Statistics.prototype.setCurrentLayout = function( + layoutCode) { + this.layoutCode_ = layoutCode; +}; + + +/** + * Gets the target type based on the given source and target. + * + * @param {string} source . + * @param {string} target . + * @return {number} The target type number value. + */ +i18n.input.chrome.inputview.Statistics.prototype.getTargetType = function( + source, target) { + if (source == target) { + return 0; + } + if (!source) { + return 3; + } + if (target.length > source.length) { + return 2; + } + return 1; +}; + + +/** + * Records the metrics for each commit. + * + * @param {number} sourceLen The source length. + * @param {number} targetLen The target length. + * @param {number} targetIndex The target index. + * @param {number} targetType The target type: + * 0: Source; 1: Correction; 2: Completion; 3: Prediction. + * @param {number} triggerType The trigger type: + * 0: BySpace; 1: ByReset; 2: ByCandidate; 3: BySymbolOrNumber; + * 4: ByDoubleSpaceToPeriod. + */ +i18n.input.chrome.inputview.Statistics.prototype.recordCommit = function( + sourceLen, targetLen, targetIndex, targetType, triggerType) { + if (!this.layoutCode_) { + return; + } + this.recordCount_('Commit.SourceLength', sourceLen); + this.recordCount_('Commit.TargetLength', targetLen); + this.recordCount_('Commit.TargetIndex', targetIndex + 1); // 1-based index. + this.recordCount_('Commit.TargetType', targetType); + this.recordCount_('Commit.TriggerType', triggerType); +}; + + +/** + * Records the VK show time for current keyboard layout. + * + * @param {number} showTimeSeconds The show time in seconds. + */ +i18n.input.chrome.inputview.Statistics.prototype.recordShowTime = function( + showTimeSeconds) { + this.recordCount_('Layout.ShowTime', showTimeSeconds); +}; + + +/** + * Records the layout switching action. + * + * @param {string} layoutCode The layout code to be switched to. + */ +i18n.input.chrome.inputview.Statistics.prototype.recordSwitch = function( + layoutCode) { + this.recordCount_('Layout.SwitchTo.' + layoutCode, 1); +}; + + +/** + * Records the special key action, which is function key like BACKSPACE, ENTER, + * etc.. + * + * @param {string} keyCode . + * @param {boolean} isComposing Whether the key is pressed with composing mode. + */ +i18n.input.chrome.inputview.Statistics.prototype.recordSpecialKey = function( + keyCode, isComposing) { + var name = 'Key.' + isComposing ? 'Composing.' : '' + keyCode; + this.recordCount_(name, 1); +}; + + +/** + * Records count value to chrome UMA framework. + * + * @param {string} name . + * @param {number} count . + * @private + */ +i18n.input.chrome.inputview.Statistics.prototype.recordCount_ = function( + name, count) { + if (!this.layoutCode_) { + return; + } + if (chrome.metricsPrivate && chrome.metricsPrivate.recordCount) { + name = 'InputView.' + name + '.' + this.layoutCode_; + chrome.metricsPrivate.recordCount(name, count); + } +}; diff --git a/third_party/google_input_tools/src/chrome/os/inputview/strokehandler.js b/third_party/google_input_tools/src/chrome/os/inputview/strokehandler.js new file mode 100644 index 0000000..520b535 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/strokehandler.js @@ -0,0 +1,252 @@ +// 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"); +// + +/** + * @fileoverview Defines the class i18n.input.hwt.StrokeHandler. + * @author fengyuan@google.com (Feng Yuan) + */ + +goog.provide('i18n.input.hwt.StrokeHandler'); +goog.provide('i18n.input.hwt.StrokeHandler.Point'); +goog.provide('i18n.input.hwt.StrokeHandler.StrokeEvent'); + + +goog.require('goog.events'); +goog.require('goog.events.Event'); +goog.require('goog.events.EventHandler'); +goog.require('goog.events.EventTarget'); +goog.require('goog.events.EventType'); +goog.require('goog.style'); +goog.require('goog.userAgent'); +goog.require('i18n.input.hwt.util'); + + + +/** + * The handler for strokes. + * + * @param {!Element} canvas The handwriting canvas. + * @param {!Document} topDocument The top document. + * @constructor + * @extends {goog.events.EventTarget} + */ +i18n.input.hwt.StrokeHandler = function(canvas, topDocument) { + goog.base(this); + + /** + * The event handler. + * + * @type {goog.events.EventHandler} + * @private + */ + this.eventHandler_ = new goog.events.EventHandler(this); + + /** + * Whether is drawing the stroke. + * + * @type {boolean} + */ + this.drawing = false; + + /** + * The canvas. + * + * @type {Element} + * @private + */ + this.canvas_ = canvas; + + // Always register mouse events. Some devices like Tablet PCs + // actually support both touch and mouse events, and the touch + // events don't get translated to mouse events on Tablet PCs. + this.eventHandler_. + listen(canvas, goog.events.EventType.MOUSEDOWN, this.onStrokeStart_). + listen(canvas, goog.events.EventType.MOUSEMOVE, this.onStroke_); + // Listen for touch events if they are supported. + if ('ontouchstart' in window) { + this.eventHandler_. + listen(canvas, goog.events.EventType.TOUCHSTART, this.onStrokeStart_). + listen(canvas, goog.events.EventType.TOUCHEND, this.onStrokeEnd_). + listen(canvas, goog.events.EventType.TOUCHCANCEL, this.onStrokeEnd_). + listen(canvas, goog.events.EventType.TOUCHMOVE, this.onStroke_); + } + + i18n.input.hwt.util.listenPageEvent(this.eventHandler_, topDocument, + goog.events.EventType.MOUSEUP, + goog.bind(this.onStrokeEnd_, this)); +}; +goog.inherits(i18n.input.hwt.StrokeHandler, goog.events.EventTarget); + + +/** + * Callback for the start of one stroke. + * + * @param {goog.events.BrowserEvent} e Event. + * @private + */ +i18n.input.hwt.StrokeHandler.prototype.onStrokeStart_ = function(e) { + this.drawing = true; + this.dispatchEvent(new i18n.input.hwt.StrokeHandler.StrokeEvent( + i18n.input.hwt.StrokeHandler.EventType.STROKE_START, + this.getPoint_(e))); + e.preventDefault(); +}; + + +/** + * Callback for the end of one stroke. + * + * @param {goog.events.BrowserEvent} e Event. + * @private + */ +i18n.input.hwt.StrokeHandler.prototype.onStrokeEnd_ = function(e) { + if (this.drawing) { + this.drawing = false; + this.dispatchEvent(new i18n.input.hwt.StrokeHandler.StrokeEvent( + i18n.input.hwt.StrokeHandler.EventType.STROKE_END, + this.getPoint_(e))); + e.preventDefault(); + } +}; + + +/** + * Callback for stroke. + * + * @param {goog.events.BrowserEvent} e Event. + * @private + */ +i18n.input.hwt.StrokeHandler.prototype.onStroke_ = function(e) { + if (this.drawing) { + this.dispatchEvent(new i18n.input.hwt.StrokeHandler.StrokeEvent( + i18n.input.hwt.StrokeHandler.EventType.STROKE, + this.getPoint_(e))); + } + e.preventDefault(); +}; + + +/** + * Given a mouse or touch event, figure out the coordinates where it occurred. + * + * @param {goog.events.BrowserEvent} e Event. + * @return {!i18n.input.hwt.StrokeHandler.Point} a point. + * @private + */ +i18n.input.hwt.StrokeHandler.prototype.getPoint_ = function(e) { + var pos = goog.style.getPageOffset(this.canvas_); + var nativeEvent = e.getBrowserEvent(); + var x, y; + if (!goog.userAgent.IE && nativeEvent.pageX && nativeEvent.pageY) { + x = nativeEvent.pageX; + y = nativeEvent.pageY; + } else { + var scrollX = (document.dir == 'rtl' ? -1 : 1) * ( + document.body.scrollLeft || + document.documentElement.scrollLeft || 0); + var scrollY = document.body.scrollTop || + document.documentElement.scrollTop || 0; + x = nativeEvent.clientX + scrollX; + y = nativeEvent.clientY + scrollY; + } + if (nativeEvent.touches != null && nativeEvent.touches.length > 0) { + x = nativeEvent.touches[0].clientX; + y = nativeEvent.touches[0].clientY; + } + return new i18n.input.hwt.StrokeHandler.Point(x - pos.x, y - pos.y, + goog.now()); +}; + + +/** + * Reset the drawing flag, in case the drawing canvas gets cleared while + * stroke is being drawn. + */ +i18n.input.hwt.StrokeHandler.prototype.reset = function() { + this.drawing = false; +}; + + +/** @override */ +i18n.input.hwt.StrokeHandler.prototype.disposeInternal = function() { + goog.dispose(this.eventHandler_); + this.eventHandler_ = null; +}; + + + +/** + * One point in the stroke. + * + * @param {number} x The x. + * @param {number} y The y. + * @param {number} time The time in milisecond. + * @constructor + */ +i18n.input.hwt.StrokeHandler.Point = function(x, y, time) { + /** + * The left offset relative to the canvas. + * + * @type {number} + */ + this.x = x; + + /** + * The top offset relative to the canvas. + * + * @type {number} + */ + this.y = y; + + /** + * The time. + * + * @type {number} + */ + this.time = time; +}; + + +/** + * Stroke events. + * + * @enum {string} + */ +i18n.input.hwt.StrokeHandler.EventType = { + STROKE: goog.events.getUniqueId('s'), + STROKE_END: goog.events.getUniqueId('se'), + STROKE_START: goog.events.getUniqueId('ss') +}; + + + +/** + * The stroke event. + * + * @param {!i18n.input.hwt.StrokeHandler.EventType} type The event type. + * @param {!i18n.input.hwt.StrokeHandler.Point} point The point. + * @constructor + * @extends {goog.events.Event} + */ +i18n.input.hwt.StrokeHandler.StrokeEvent = function(type, point) { + goog.base(this, type); + + /** + * The point. + * + * @type {!i18n.input.hwt.StrokeHandler.Point} + */ + this.point = point; +}; +goog.inherits(i18n.input.hwt.StrokeHandler.StrokeEvent, goog.events.Event); diff --git a/third_party/google_input_tools/src/chrome/os/inputview/swipedirection.js b/third_party/google_input_tools/src/chrome/os/inputview/swipedirection.js new file mode 100644 index 0000000..9f466bb --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/swipedirection.js @@ -0,0 +1,29 @@ +// 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.SwipeDirection'); + + + +/** + * The swipe directin. + * + * @enum {number} + */ +i18n.input.chrome.inputview.SwipeDirection = { + UP: 0x1, + DOWN: 0x2, + LEFT: 0x4, + RIGHT: 0x8 +}; + diff --git a/third_party/google_input_tools/src/chrome/os/inputview/util.js b/third_party/google_input_tools/src/chrome/os/inputview/util.js new file mode 100644 index 0000000..d7667ea --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/inputview/util.js @@ -0,0 +1,296 @@ +// 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.util'); + +goog.require('goog.string'); +goog.require('goog.style'); + + +goog.scope(function() { +var util = i18n.input.chrome.inputview.util; + + +/** + * The mapping between the real character and its replacement for display. + * + * @type {!Object.<string, string>} + */ +util.DISPLAY_MAPPING = { + '\u0300' : '\u0060', + '\u0301' : '\u00B4', + '\u0302' : '\u02C6', + '\u0303' : '\u02DC', + '\u0304' : '\u02c9', + '\u0305' : '\u00AF', + '\u0306' : '\u02D8', + '\u0307' : '\u02D9', + '\u0308' : '\u00a8', + '\u0309' : '\u02C0', + '\u030A' : '\u02DA', + '\u030B' : '\u02DD', + '\u030C' : '\u02C7', + '\u030D' : '\u02C8', + '\u030E' : '\u0022', + '\u0327' : '\u00B8', + '\u0328' : '\u02DB', + '\u0345' : '\u037A', + '\u030F' : '\u030F\u0020', + '\u031B' : '\u031B\u0020', + '\u0323' : '\u0323\u0020' +}; + + +/** + * The keysets using US keyboard layouts. + * + * @type {!Array.<string>} + */ +util.KEYSETS_USE_US = [ + 'array', + 'cangjie', + 'dayi', + 'jp_us', + 'pinyin-zh-CN', + 'pinyin-zh-TW', + 'quick', + 't13n', + 'wubi', + 'zhuyin' +]; + + +/** + * The keysets that have en switcher key. + * + * @type {!Array.<string>} + */ +util.KEYSETS_HAVE_EN_SWTICHER = [ + // When other keysets that use us add the enswitcher key, + // should move them to this array. + 'pinyin-zh-CN' +]; + + +/** + * A regular expression for the end of a sentence. + * + * @private {!RegExp} + */ +util.END_SENTENCE_REGEX_ = /[\.\?!] +$/; + + +/** + * The regex of characters support dead key. + * + * @type {!RegExp} + * @private + */ +util.REGEX_CHARACTER_SUPPORT_DEADKEY_ = + /^[a-zA-ZæÆœŒΑΕΗΙΟΥΩαεηιυοωϒ]+$/; + + +/** + * The regex of characters supported in language module. + * + * @type {!RegExp} + */ +util.REGEX_LANGUAGE_MODEL_CHARACTERS = + /(?=[^\u00d7\u00f7])[a-z\-\'\u00c0-\u017F]/i; + + +/** + * Splits a value to pieces according to the weights. + * + * @param {!Array.<number>} weightArray The weight array. + * @param {number} totalValue The total value. + * @return {!Array.<number>} The splitted values. + */ +util.splitValue = function(weightArray, totalValue) { + if (weightArray.length == 0) { + return []; + } + + if (weightArray.length == 1) { + return [totalValue]; + } + + var totalWeight = 0; + for (var i = 0; i < weightArray.length; i++) { + totalWeight += weightArray[i]; + } + var tmp = totalValue / totalWeight; + var values = []; + var totalFlooredValue = 0; + var diffs = []; + for (var i = 0; i < weightArray.length; i++) { + var result = weightArray[i] * tmp; + values.push(result); + diffs.push(result - Math.floor(result)); + totalFlooredValue += Math.floor(result); + } + var diff = totalValue - totalFlooredValue; + + // Distributes the rest pixels to values who lose most. + for (var i = 0; i < diff; i++) { + var max = 0; + var index = 0; + for (var j = 0; j < diffs.length; j++) { + if (diffs[j] > max) { + max = diffs[j]; + index = j; + } + } + values[index] += 1; + diffs[index] = 0; + } + for (var i = 0; i < values.length; i++) { + values[i] = Math.floor(values[i]); + } + return values; +}; + + +/** + * Gets the value of a property. + * + * @param {Element} elem The element. + * @param {string} property The property name. + * @return {number} The value. + */ +util.getPropertyValue = function(elem, property) { + var value = goog.style.getComputedStyle(elem, property); + if (value) { + return parseInt(value.replace('px', ''), 10); + } + return 0; +}; + + +/** + * To upper case. + * + * @param {string} character The character. + * @return {string} The uppercase of the character. + */ +util.toUpper = function(character) { + if (character == '\u00b5') { + return '\u005b5'; + } else { + return character.toUpperCase(); + } +}; + + +/** + * To lower case. + * + * @param {string} character The character. + * @return {string} The lower case of the character. + */ +util.toLower = function(character) { + if (character == '\u0049') { + return '\u0131'; + } + return character.toLowerCase(); +}; + + +/** + * Is this character trigger commit. + * + * @param {string} character The character. + * @return {boolean} True to trigger commit. + */ +util.isCommitCharacter = function(character) { + if (util.DISPLAY_MAPPING[character] || + util.REGEX_LANGUAGE_MODEL_CHARACTERS.test( + character)) { + return false; + } + + return true; +}; + + +/** + * Some unicode character can't be shown in the web page, use a replacement + * instead. + * + * @param {string} invisibleCharacter The character can't be shown. + * @return {string} The replacement. + */ +util.getVisibleCharacter = function(invisibleCharacter) { + var map = util.DISPLAY_MAPPING; + if (map[invisibleCharacter]) { + return map[invisibleCharacter]; + } + return invisibleCharacter; +}; + + +/** + * Whether this is a letter key. + * + * @param {!Array.<string>} characters The characters. + * @return {boolean} True if this is a letter key. + */ +util.isLetterKey = function(characters) { + if (characters[1] == util.toUpper( + characters[0]) || characters[1] == util. + toLower(characters[0])) { + return true; + } + return false; +}; + + +/** + * True if this character supports dead key combination. + * + * @param {string} character The character. + * @return {boolean} True if supports the dead key combination. + */ +util.supportDeadKey = function(character) { + return util.REGEX_CHARACTER_SUPPORT_DEADKEY_. + test(character); +}; + + +/** + * True if we need to do the auto-capitalize. + * + * @param {string} text . + * @return {boolean} . + */ +util.needAutoCap = function(text) { + if (goog.string.isEmpty(text)) { + return false; + } else { + return util.END_SENTENCE_REGEX_.test(text); + } +}; + + +/** + * Returns the configuration file name from the keyboard code. + * + * @param {string} keyboardCode The keyboard code. + * @return {string} The config file name which contains the keyset. + */ +util.getConfigName = function(keyboardCode) { + // Strips out all the suffixes in the keyboard code. + return keyboardCode.replace(/\..*$/, ''); +}; + +}); // goog.scope diff --git a/third_party/google_input_tools/src/chrome/os/keyboard/eventtype.js b/third_party/google_input_tools/src/chrome/os/keyboard/eventtype.js new file mode 100644 index 0000000..9c0f4f0 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/keyboard/eventtype.js @@ -0,0 +1,42 @@ +// 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"); +// +// Copyright 2013 The ChromeOS VK Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview The event types for keyboard. + */ + +goog.provide('i18n.input.chrome.vk.EventType'); + + +/** + * The event types. + * + * @enum {string} + */ +i18n.input.chrome.vk.EventType = { + LAYOUT_LOADED: 'lld' +}; diff --git a/third_party/google_input_tools/src/chrome/os/keyboard/keycode.js b/third_party/google_input_tools/src/chrome/os/keyboard/keycode.js new file mode 100644 index 0000000..4ef33a3 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/keyboard/keycode.js @@ -0,0 +1,86 @@ +// 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"); +// +// Copyright 2013 The ChromeOS VK Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Defines all the key codes and key codes map. + */ + +goog.provide('i18n.input.chrome.vk.KeyCode'); + + +/** + * The standard 101 keyboard keys. Each char represent the key code of each key. + * + * @type {string} + */ +i18n.input.chrome.vk.KeyCode.CODES101 = + '\u00c01234567890\u00bd\u00bb' + + 'QWERTYUIOP\u00db\u00dd\u00dc' + + 'ASDFGHJKL\u00ba\u00de' + + 'ZXCVBNM\u00bc\u00be\u00bf' + + '\u0020'; + + +/** + * The standard 102 keyboard keys. + * + * @type {string} + */ +i18n.input.chrome.vk.KeyCode.CODES102 = + '\u00c01234567890\u00bd\u00bb' + + 'QWERTYUIOP\u00db\u00dd' + + 'ASDFGHJKL\u00ba\u00de\u00dc' + + '\u00e2ZXCVBNM\u00bc\u00be\u00bf' + + '\u0020'; + + +/** + * The standard 101 keyboard keys, including the BS/TAB/CAPS/ENTER/SHIFT/ALTGR. + * Each char represent the key code of each key. + * + * @type {string} + */ +i18n.input.chrome.vk.KeyCode.ALLCODES101 = + '\u00c01234567890\u00bd\u00bb\u0008' + + '\u0009QWERTYUIOP\u00db\u00dd\u00dc' + + '\u0014ASDFGHJKL\u00ba\u00de\u000d' + + '\u0010ZXCVBNM\u00bc\u00be\u00bf\u0010' + + '\u0111\u0020\u0111'; + + +/** + * The standard 102 keyboard keys, including the BS/TAB/CAPS/ENTER/SHIFT/ALTGR. + * Each char represent the key code of each key. + * + * @type {string} + */ +i18n.input.chrome.vk.KeyCode.ALLCODES102 = + '\u00c01234567890\u00bd\u00bb\u0008' + + '\u0009QWERTYUIOP\u00db\u00dd\u000d' + + '\u0014ASDFGHJKL\u00ba\u00de\u00dc\u000d' + + '\u0010\u00e2ZXCVBNM\u00bc\u00be\u00bf\u0010' + + '\u0111\u0020\u0111'; diff --git a/third_party/google_input_tools/src/chrome/os/keyboard/layoutevent.js b/third_party/google_input_tools/src/chrome/os/keyboard/layoutevent.js new file mode 100644 index 0000000..2bc0a13 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/keyboard/layoutevent.js @@ -0,0 +1,64 @@ +// 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"); +// +// Copyright 2013 The ChromeOS VK Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview The definition of layout event. + */ + +goog.provide('i18n.input.chrome.vk.LayoutEvent'); + +goog.require('goog.events.Event'); + + + +/** + * The Layout Event. + * + * @param {i18n.input.chrome.vk.EventType} type The event type. + * @param {Object} layoutView The layoutView object. See ParsedLayout for + * details. + * @constructor + * @extends {goog.events.Event} + */ +i18n.input.chrome.vk.LayoutEvent = function(type, layoutView) { + goog.base(this, type); + + /** + * The layout view object. + * + * @type {Object} + */ + this.layoutView = layoutView; + + /** + * The layout code. + * + * @type {?string} + */ + this.layoutCode = layoutView ? layoutView.id : null; +}; +goog.inherits(i18n.input.chrome.vk.LayoutEvent, goog.events.Event); diff --git a/third_party/google_input_tools/src/chrome/os/keyboard/model.js b/third_party/google_input_tools/src/chrome/os/keyboard/model.js new file mode 100644 index 0000000..3f940ed --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/keyboard/model.js @@ -0,0 +1,424 @@ +// 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"); +// +// Copyright 2013 The ChromeOS VK Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Definition of Model class. + * It is responsible for dynamically loading the layout JS files. It + * interprets the layout info and provides the function of getting + * transformed chars and recording history states to Model. + * It notifies View via events when layout info changes. + * This is the Model of MVC pattern. + */ + +goog.provide('i18n.input.chrome.vk.Model'); + +goog.require('goog.events.EventTarget'); +goog.require('goog.net.jsloader'); +goog.require('goog.object'); +goog.require('goog.string'); +goog.require('i18n.input.chrome.vk.EventType'); +goog.require('i18n.input.chrome.vk.LayoutEvent'); +goog.require('i18n.input.chrome.vk.ParsedLayout'); + + + +/** + * Creates the Model object. + * + * @constructor + * @extends {goog.events.EventTarget} + */ +i18n.input.chrome.vk.Model = function() { + goog.base(this); + + /** + * The registered layouts object. + * Its format is {<layout code>: <parsed layout obj>}. + * + * @type {!Object.<!i18n.input.chrome.vk.ParsedLayout|boolean>} + * @private + */ + this.layouts_ = {}; + + /** + * The active layout code. + * + * @type {string} + * @private + */ + this.activeLayout_ = ''; + + /** + * The layout code of which the layout is "being activated" when the layout + * hasn't been loaded yet. + * + * @type {string} + * @private + */ + this.delayActiveLayout_ = ''; + + /** + * History state used for ambiguous transforms. + * + * @type {!Object} + * @private + */ + this.historyState_ = { + previous: {text: '', transat: -1}, + ambi: '', + current: {text: '', transat: -1} + }; + + // Exponses the onLayoutLoaded so that the layout JS can call it back. + goog.exportSymbol('cros_vk_loadme', goog.bind(this.onLayoutLoaded_, this)); +}; +goog.inherits(i18n.input.chrome.vk.Model, goog.events.EventTarget); + + +/** + * Loads the layout in the background. + * + * @param {string} layoutCode The layout will be loaded. + */ +i18n.input.chrome.vk.Model.prototype.loadLayout = function(layoutCode) { + if (!layoutCode) return; + + var parsedLayout = this.layouts_[layoutCode]; + // The layout is undefined means not loaded, false means loading. + if (parsedLayout == undefined) { + this.layouts_[layoutCode] = false; + i18n.input.chrome.vk.Model.loadLayoutScript_(layoutCode); + } else if (parsedLayout) { + this.dispatchEvent(new i18n.input.chrome.vk.LayoutEvent( + i18n.input.chrome.vk.EventType.LAYOUT_LOADED, + /** @type {!Object} */ (parsedLayout))); + } +}; + + +/** + * Activate layout by setting the current layout. + * + * @param {string} layoutCode The layout will be set as current layout. + */ +i18n.input.chrome.vk.Model.prototype.activateLayout = function( + layoutCode) { + if (!layoutCode) return; + + if (this.activeLayout_ != layoutCode) { + var parsedLayout = this.layouts_[layoutCode]; + if (parsedLayout) { + this.activeLayout_ = layoutCode; + this.delayActiveLayout_ = ''; + this.clearHistory(); + } else if (parsedLayout == false) { // Layout being loaded? + this.delayActiveLayout_ = layoutCode; + } + } +}; + + +/** + * Gets the current layout. + * + * @return {string} The current layout code. + */ +i18n.input.chrome.vk.Model.prototype.getCurrentLayout = function() { + return this.activeLayout_; +}; + + +/** + * Predicts whether there would be future transforms for the history text. + * + * @return {number} The matched position. Returns -1 for no match. + */ +i18n.input.chrome.vk.Model.prototype.predictHistory = function() { + if (!this.activeLayout_ || !this.layouts_[this.activeLayout_]) { + return -1; + } + var parsedLayout = this.layouts_[this.activeLayout_]; + var history = this.historyState_; + var text, transat; + if (history.ambi) { + text = history.previous.text; + transat = history.previous.transat; + // Tries to predict transform for previous history. + if (transat > 0) { + text = text.slice(0, transat) + '\u001d' + text.slice(transat) + + history.ambi; + } else { + text += history.ambi; + } + if (parsedLayout.predictTransform(text) >= 0) { + // If matched previous history, always return 0 because outside will use + // this to keep the composition text. + return 0; + } + } + // Tries to predict transform for current history. + text = history.current.text; + transat = history.current.transat; + if (transat >= 0) { + text = text.slice(0, transat) + '\u001d' + text.slice(transat); + } + var pos = parsedLayout.predictTransform(text); + if (transat >= 0 && pos > transat) { + // Adjusts the pos for removing the temporary \u001d character. + pos--; + } + return pos; +}; + + +/** + * Translates the key code into the chars to put into the active input box. + * + * @param {string} chars The key commit chars. + * @param {string} charsBeforeCaret The chars before the caret in the active + * input box. This will be used to compare with the history states. + * @return {Object} The replace chars object whose 'back' means delete how many + * chars back from the caret, and 'chars' means the string insert after the + * deletion. Returns null if no result. + */ +i18n.input.chrome.vk.Model.prototype.translate = function( + chars, charsBeforeCaret) { + if (!this.activeLayout_ || !chars) { + return null; + } + var parsedLayout = this.layouts_[this.activeLayout_]; + if (!parsedLayout) { + return null; + } + + this.matchHistory_(charsBeforeCaret); + var result, history = this.historyState_; + if (history.ambi) { + // If ambi is not empty, it means some ambi chars has been typed + // before. e.g. ka->k, kaa->K, typed 'ka', and now typing 'a': + // history.previous == 'k',1 + // history.current == 'k',1 + // history.ambi == 'a' + // So now we should get transform of 'k\u001d' + 'aa'. + result = parsedLayout.transform( + history.previous.text, history.previous.transat, + history.ambi + chars); + // Note: result.back could be negative number. In such case, we should give + // up the transform result. This is to be compatible the old vk behaviors. + if (result && result.back < 0) { + result = null; + } + } + if (result) { + // Because the result is related to previous history, adjust the result so + // that it is related to current history. + var prev = history.previous.text; + prev = prev.slice(0, prev.length - result.back); + prev += result.chars; + result.back = history.current.text.length; + result.chars = prev; + } else { + // If no ambi chars or no transforms for ambi chars, try to match the + // regular transforms. In above case, if now typing 'b', we should get + // transform of 'k\u001d' + 'b'. + result = parsedLayout.transform( + history.current.text, history.current.transat, chars); + } + // Updates the history state. + if (parsedLayout.isAmbiChars(history.ambi + chars)) { + if (!history.ambi) { + // Empty ambi means chars should be the first ambi chars. + // So now we should set the previous. + history.previous = goog.object.clone(history.current); + } + history.ambi += chars; + } else if (parsedLayout.isAmbiChars(chars)) { + // chars could match ambi regex when ambi+chars cannot. + // In this case, record the current history to previous, and set ambi as + // chars. + history.previous = goog.object.clone(history.current); + history.ambi = chars; + } else { + history.previous.text = ''; + history.previous.transat = -1; + history.ambi = ''; + } + // Updates the history text per transform result. + var text = history.current.text; + var transat = history.current.transat; + if (result) { + text = text.slice(0, text.length - result.back); + text += result.chars; + transat = text.length; + } else { + text += chars; + // This function doesn't return null. So if result is null, fill it. + result = {back: 0, chars: chars}; + } + // The history text cannot cannot contain SPACE! + var spacePos = text.lastIndexOf(' '); + if (spacePos >= 0) { + text = text.slice(spacePos + 1); + if (transat > spacePos) { + transat -= spacePos + 1; + } else { + transat = -1; + } + } + history.current.text = text; + history.current.transat = transat; + + return result; +}; + + +/** + * Wether the active layout has transforms defined. + * + * @return {boolean} True if transforms defined, false otherwise. + */ +i18n.input.chrome.vk.Model.prototype.hasTransforms = function() { + var parsedLayout = this.layouts_[this.activeLayout_]; + return !!parsedLayout && !!parsedLayout.transforms; +}; + + +/** + * Processes the backspace key. It affects the history state. + * + * @param {string} charsBeforeCaret The chars before the caret in the active + * input box. This will be used to compare with the history states. + */ +i18n.input.chrome.vk.Model.prototype.processBackspace = function( + charsBeforeCaret) { + this.matchHistory_(charsBeforeCaret); + + var history = this.historyState_; + // Reverts the current history. If the backspace across over the transat pos, + // clean it up. + var text = history.current.text; + if (text) { + text = text.slice(0, text.length - 1); + history.current.text = text; + if (history.current.transat > text.length) { + history.current.transat = text.length; + } + + text = history.ambi; + if (text) { // If there is ambi text, remove the last char in ambi. + history.ambi = text.slice(0, text.length - 1); + } + // Prev history only exists when ambi is not empty. + if (!history.ambi) { + history.previous = {text: '', transat: -1}; + } + } else { + // Cleans up the previous history. + history.previous = {text: '', transat: -1}; + history.ambi = ''; + // Cleans up the current history. + history.current = goog.object.clone(history.previous); + } +}; + + +/** + * Callback when layout loaded. + * + * @param {!Object} layout The layout object passed from the layout JS's loadme + * callback. + * @private + */ +i18n.input.chrome.vk.Model.prototype.onLayoutLoaded_ = function(layout) { + var parsedLayout = new i18n.input.chrome.vk.ParsedLayout(layout); + if (parsedLayout.id) { + this.layouts_[parsedLayout.id] = parsedLayout; + } + if (this.delayActiveLayout_ == layout.id) { + this.activateLayout(this.delayActiveLayout_); + this.delayActiveLayout_ = ''; + } + this.dispatchEvent(new i18n.input.chrome.vk.LayoutEvent( + i18n.input.chrome.vk.EventType.LAYOUT_LOADED, parsedLayout)); +}; + + +/** + * Matches the given text to the last transformed text. Clears history if they + * are not matched. + * + * @param {string} text The text to be matched. + * @private + */ +i18n.input.chrome.vk.Model.prototype.matchHistory_ = function(text) { + var hisText = this.historyState_.current.text; + if (!hisText || !text || !(goog.string.endsWith(text, hisText) || + goog.string.endsWith(hisText, text))) { + this.clearHistory(); + } +}; + + +/** + * Clears the history state. + */ +i18n.input.chrome.vk.Model.prototype.clearHistory = function() { + this.historyState_.ambi = ''; + this.historyState_.previous = {text: '', transat: -1}; + this.historyState_.current = goog.object.clone(this.historyState_.previous); +}; + + +/** + * Prunes the history state to remove a number of chars at beginning. + * + * @param {number} count The count of chars to be removed. + */ +i18n.input.chrome.vk.Model.prototype.pruneHistory = function(count) { + var pruneFunc = function(his) { + his.text = his.text.slice(count); + if (his.transat > 0) { + his.transat -= count; + if (his.transat <= 0) { + his.transat = -1; + } + } + }; + pruneFunc(this.historyState_.previous); + pruneFunc(this.historyState_.current); +}; + + +/** + * Loads the script for a layout. + * + * @param {string} layoutCode The layout code. + * @private + */ +i18n.input.chrome.vk.Model.loadLayoutScript_ = function(layoutCode) { + goog.net.jsloader.load('layouts/' + layoutCode + '.js'); +}; diff --git a/third_party/google_input_tools/src/chrome/os/keyboard/parsedlayout.js b/third_party/google_input_tools/src/chrome/os/keyboard/parsedlayout.js new file mode 100644 index 0000000..d414bea --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/keyboard/parsedlayout.js @@ -0,0 +1,423 @@ +// 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"); +// +// Copyright 2013 The ChromeOS VK Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Defines the parsed layout object which will do layout parsing + * and expose the keymappings and the transforms to Model. + */ + +goog.provide('i18n.input.chrome.vk.ParsedLayout'); + +goog.require('goog.object'); +goog.require('i18n.input.chrome.vk.KeyCode'); + + + +/** + * Creates the parsed layout object per the raw layout info. + * + * @param {!Object} layout The raw layout object defined in the + * xxx_layout.js. + * @constructor + */ +i18n.input.chrome.vk.ParsedLayout = function(layout) { + /** + * The layout code (a.k.a. id). + * + * @type {string} + */ + this.id = layout['id']; + + /** + * The view object needed by UI rendering, including the key + * mappings. Some extra keys are not appear in following, which are + * '', 's', 'l', 'sl', 'cl', 'sc', 'scl'. They define the key mappings + * for each keyboard mode: + * '' means normal; + * 's' means SHIFT; + * 'l' means CAPSLOCK; + * 'c' means CTRL+ALT. + * Those modes will be filled when parsing the raw layout. + * If certain modes are not defined by the raw layout, this.view.<mode> + * won't be filled in. + * The mode format is: { + * '<keyChar>': ['<disp type(S|P)>', '<disp chars>', '<commit chars>'] + * }. + * + * @type {!Object} + */ + this.view = { + 'id': layout['id'], + 'title': layout['title'], + 'isRTL': layout['direction'] == 'rtl', + 'is102': !!layout['is102Keyboard'], + 'mappings': goog.object.create([ + '', null, + 's', null, + 'c', null, + 'l', null, + 'sc', null, + 'cl', null, + 'sl', null, + 'scl', null + ]) + }; + + /** + * The parsed layout transforms. There are only 3 elements of this array. + * !st is the long exgexp to match, 2nd is the map of: + * <match location>: [<regexp>, <replacement>]. + * 3rd/4th are the regexp for prefix matches. + * + * @type {Array.<!Object>} + */ + this.transforms = null; + + /** + * The parsed layout ambiguous chars. + * + * @type {Object} + * @private + */ + this.ambiRegex_ = null; + + // Parses the key mapping & transforms of the layout. + this.parseKeyMappings_(layout); + this.parseTransforms_(layout); +}; + + +/** + * Parses the key mappings of the given layout. + * + * @param {!Object} layout The raw layout object. It's format is: + * id: <layout id> in {string} + * title: <layout title> in {string} + * direction: 'rtl' or 'ltr' + * is102Keyboard: True if vk is 102, False/undefined for 101 + * mappings: key map in {Object.<string,string>} + * '': keycodes (each char's charCode represents keycode) in normal state + * s: keycodes in SHIFT state + * c: keycodes in ALTGR state + * l: keycodes in CAPSLOCK state + * <the states could be combined, e.g. ',s,sc,sl,scl'> + * transform: in {Object.<string,string>} + * <regexp>: <replacement> + * historyPruneRegex: <regexp string to represent the ambiguities>. + * @private + */ +i18n.input.chrome.vk.ParsedLayout.prototype.parseKeyMappings_ = function( + layout) { + var codes = this.view['is102'] ? i18n.input.chrome.vk.KeyCode.CODES102 : + i18n.input.chrome.vk.KeyCode.CODES101; + + var mappings = layout['mappings']; + for (var m in mappings) { + var map = mappings[m]; + var modes = m.split(/,/); + if (modes.join(',') != m) { + modes.push(''); // IE splits 'a,b,' into ['a','b'] + } + var parsed = {}; + // Example for map is like: + // 1) {'': '\u00c0123456...', ...} + // 2) {'QWERT': 'QWERT', ...} + // 3) {'A': 'aa', ...} + // 4) {'BCD': '{{bb}}cd', ...} + // 5) {'EFG': '{{S||e||ee}}FG', ...} + // 6) {'HI': '{{P||12||H}}i', ...} + for (var from in map) { + // In case #1, from is '', to is '\u00c0123456...'. + // In case #3, from is 'A', to is 'aa'. + var to = map[from]; + if (from == '') { + from = codes; + // If is 102 keyboard, modify 'to' to be compatible with the old vk. + if (this.view['is102']) { + // Moves the 26th char {\} to be the 38th char (after {'}). + var normalizedTo = to.slice(0, 25); + normalizedTo += to.slice(26, 37); + normalizedTo += to.charAt(25); + normalizedTo += to.slice(37); + to = normalizedTo; + } + } + // Replaces some chars for backward compatibility to old layout + // definitions. + from = from.replace('m', '\u00bd'); + from = from.replace('=', '\u00bb'); + from = from.replace(';', '\u00ba'); + if (from.length == 1) { + // Case #3: single char map to chars. + parsed[from] = ['S', to, to]; + } else { + var j = 0; + for (var i = 0, c; c = from.charAt(i); ++i) { + var t = to.charAt(j++); + if (t == to.charAt(j) && t == '{') { + // Case #4/5/6: {{}} to define single char map to chars. + var k = to.indexOf('}}', j); + if (k < j) break; + var s = to.slice(j + 1, k); + var parts = s.split('||'); + if (parts.length == 3) { + // Case #5/6: button/commit chars seperation. + parsed[c] = parts; + } else if (parts.length == 1) { + // Case #4. + parsed[c] = ['S', s, s]; + } + j = k + 2; + } else { + // Normal case: single char map to according single char. + parsed[c] = ['S', t, t]; + } + } + } + } + for (var i = 0, mode; mode = modes[i], mode != undefined; ++i) { + this.view['mappings'][mode] = parsed; + } + } +}; + + +/** + * Prefixalizes the regexp string. + * + * @param {string} re_str The original regexp string. + * @return {string} The prefixalized the regexp string. + * @private + */ +i18n.input.chrome.vk.ParsedLayout.prototype.prefixalizeRegexString_ = function( + re_str) { + // Makes sure [...\[\]...] won't impact the later replaces. + re_str = re_str.replace(/\\./g, function(m) { + if (/^\\\[/.test(m)) { + return '\u0001'; + } + if (/^\\\]/.test(m)) { + return '\u0002'; + } + return m; + }); + // Prefixalizes. + re_str = re_str.replace(/\\.|\[[^\[\]]*\]|\{.*\}|[^\|\\\(\)\[\]\{\}\*\+\?]/g, + function(m) { + if (/^\{/.test(m)) { + return m; + } + return '(?:' + m + '|$)'; + }); + // Restores the \[\]. + re_str = re_str.replace(/\u0001/g, '\\['); + re_str = re_str.replace(/\u0002/g, '\\]'); + return re_str; +}; + + +/** + * Parses the transforms of the given layout. + * + * @param {!Object} layout The raw layout object. It's format is: + * id: <layout id> in {string} + * title: <layout title> in {string} + * direction: 'rtl' or 'ltr' + * is102Keyboard: True if vk is 102, False/undefined for 101 + * mappings: key map in {Object.<string,string>} + * '': keycodes (each char's charCode represents keycode) in normal state + * s: keycodes in SHIFT state + * c: keycodes in ALTGR state + * l: keycodes in CAPSLOCK state + * <the states could be combined, e.g. ',s,sc,sl,scl'> + * transform: in {Object.<string,string>} + * <regexp>: <replacement> + * historyPruneRegex: <regexp string to represent the ambiguities>. + * @private + */ +i18n.input.chrome.vk.ParsedLayout.prototype.parseTransforms_ = function( + layout) { + var transforms = layout['transform']; + if (transforms) { + // regobjs is RegExp objects of the regexp string. + // regexsalone will be used to get the long regexp which concats all the + // transform regexp as (...$)|(...$)|... + // The long regexp is needed because it is ineffecient to match each regexp + // one by one. Instead, we match the long regexp only once. But we need to + // know where the match happens and which replacement we need to use. + // So regobjs will hold the map between the match location and the + // regexp/replacement. + var regobjs = [], regexesalone = [], partialRegexs = []; + // sum_numgrps is the index of current reg group for future matching. + // Don't care about the whole string in array index 0. + var sum_numgrps = 1; + for (var regex in transforms) { + var regobj = new RegExp(regex + '$'); + var repl = transforms[regex]; + regobjs[sum_numgrps] = [regobj, repl]; + regexesalone.push('(' + regex + '$)'); + partialRegexs.push('^(' + this.prefixalizeRegexString_(regex) + ')'); + // The match should happen to count braces. + var grpCountRegexp = new RegExp(regex + '|.*'); + // The length attribute would count whole string as well. + // However, that extra count 1 is compensated by + // extra braces added. + var numgrps = grpCountRegexp.exec('').length; + sum_numgrps += numgrps; + } + var longregobj = new RegExp(regexesalone.join('|')); + // Saves 2 long regexp objects for later prefix matching. + // The reason to save a regexp with '\u0001' is to make sure the whole + // string won't match as a prefix for the whole pattern. For example, + // 'abc' shouldn't match /abc/. + // In above case, /abc/ is prefixalized as re = /(a|$)(b|$)(c|$)/. + // 'a', 'ab' & 'abc' can all match re. + // So make another re2 = /(a|$)(b|$)(c|$)\u0001/, therefore, 'abc' will + // fail to match. Finally, we can use this checks to make sure the prefix + // match: "s matches re but it doesn't match re2". + var prefixregobj = new RegExp(partialRegexs.join('|')); + // Uses reverse-ordered regexp for prefix matching. Details are explained + // in predictTransform(). + var prefixregobj2 = new RegExp(partialRegexs.reverse().join('|')); + this.transforms = [longregobj, regobjs, prefixregobj, prefixregobj2]; + } + + var hisPruReg = layout['historyPruneRegex']; + if (hisPruReg) { + this.ambiRegex_ = new RegExp('^(' + hisPruReg + ')$'); + } +}; + + +/** + * Predicts whether there would be future transforms for the given string. + * + * @param {string} text The given string. + * @return {number} The matched position in the string. Returns -1 for no match. + */ +i18n.input.chrome.vk.ParsedLayout.prototype.predictTransform = function(text) { + if (!this.transforms || !text) { + return -1; + } + for (var i = 0; i < text.length; i++) { + var s = text.slice(i - text.length); + // Uses multiple mathches to make sure the prefix match. + // Refers to comments in parseTransforms_() method. + var matches = s.match(this.transforms[2]); + if (matches && matches[0]) { + for (var j = 1; j < matches.length && !matches[j]; j++) {} + var matchedIndex = j; + // Ties to match the reversed regexp and see whether the matched indexes + // are pointed to the same rule. + matches = s.match(this.transforms[3]); + if (matches && matches[0]) { // This should always match! + for (var j = 1; j < matches.length && !matches[j]; j++) {} + if (matchedIndex != matches.length - j) { + // If the matched and reverse-matched index are not the same, it + // means the string must be a prefix, because the layout transforms + // shouldn't have duplicated transforms. + return i; + } else { + // Gets the matched rule regexp, and revise it to add a never-matched + // char X in the end. And tries to match it with s+X. + // If matched, it means the s is a full match instead of a prefix + // match. + var re = this.transforms[1][matchedIndex][0]; + re = new RegExp(re.toString().match(/\/(.*)\//)[1] + '\u0001'); + if (!(s + '\u0001').match(re)) { + return i; + } + } + } + } + } + return -1; +}; + + +/** + * Applies the layout transform and gets the result. + * + * @param {string} prevstr The previous text. + * @param {number} transat The position of previous transform. If it's -1, + * it means no transform happened. + * @param {string} ch The new chars currently added to prevstr. + * @return {Object} The transform result. It's format is: + * {back: <the number of chars to be deleted in the end of the prevstr>, + * chars: <the chars to add at the tail after the deletion>}. + * If there is no transform applies, return null. + */ +i18n.input.chrome.vk.ParsedLayout.prototype.transform = function( + prevstr, transat, ch) { + if (!this.transforms) return null; + + var str; + if (transat > 0) { + str = prevstr.slice(0, transat) + '\u001d' + + prevstr.slice(transat) + ch; + } else { + str = prevstr + ch; + } + var longr = this.transforms[0]; + var matchArr = longr.exec(str); + if (matchArr) { + var rs = this.transforms[1]; + + for (var i = 1; i < matchArr.length && !matchArr[i]; i++) {} + var matchGroup = i; + + var regobj = rs[matchGroup][0]; + var repl = rs[matchGroup][1]; + var m = regobj.exec(str); + + // String visible to user does not have LOOK_BEHIND_SEP_ and chars. + // So need to discount them in backspace count. + var rmstr = str.slice(m.index); + var numseps = rmstr.search('\u001d') > -1 ? 1 : 0; + var backlen = rmstr.length - numseps - ch.length; + + var newstr = str.replace(regobj, repl); + var replstr = newstr.slice(m.index); + replstr = replstr.replace('\u001d', ''); + + return {back: backlen, chars: replstr}; + } + + return null; +}; + + +/** + * Gets whether the given chars is ambiguious chars. + * + * @param {string} chars The chars to be judged. + * @return {boolean} True if given chars is ambiguious chars, false + * otherwise. + */ +i18n.input.chrome.vk.ParsedLayout.prototype.isAmbiChars = function(chars) { + return this.ambiRegex_ ? !!this.ambiRegex_.exec(chars) : false; +}; diff --git a/third_party/google_input_tools/src/chrome/os/message/contenttype.js b/third_party/google_input_tools/src/chrome/os/message/contenttype.js new file mode 100644 index 0000000..0a50da3 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/message/contenttype.js @@ -0,0 +1,31 @@ +// 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.message.ContextType'); + + +/** + * The message type. + * + * @enum {string} + */ +i18n.input.chrome.message.ContextType = { + DEFAULT: 'text', + EMAIL: 'email', + PASSWORD: 'password', + URL: 'url', + NUMBER: 'number' +}; + + + diff --git a/third_party/google_input_tools/src/chrome/os/message/event.js b/third_party/google_input_tools/src/chrome/os/message/event.js new file mode 100644 index 0000000..7972399 --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/message/event.js @@ -0,0 +1,40 @@ +// 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.message.Event'); + +goog.require('goog.events.Event'); + + + +/** + * The message event. + * + * @param {i18n.input.chrome.message.Type} type . + * @param {Object} msg . + * @param {Function=} opt_sendResponse . + * @constructor + * @extends {goog.events.Event} + */ +i18n.input.chrome.message.Event = function(type, msg, opt_sendResponse) { + goog.base(this, type); + + /** @type {Object} */ + this.msg = msg; + + /** @type {Function} */ + this.sendResponse = opt_sendResponse || null; +}; +goog.inherits(i18n.input.chrome.message.Event, + goog.events.Event); + diff --git a/third_party/google_input_tools/src/chrome/os/message/name.js b/third_party/google_input_tools/src/chrome/os/message/name.js new file mode 100644 index 0000000..efab8df --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/message/name.js @@ -0,0 +1,62 @@ +// 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.message.Name'); + +/** + * The message attribute name. + * + * @enum {string} + */ +i18n.input.chrome.message.Name = { + ALT_KEY: 'altKey', + CANDIDATE: 'candidate', + CANDIDATES: 'candidates', + CANDIDATE_ID: 'candidateID', + CODE: 'code', + CONTEXT_ID: 'contextID', + CONTEXT_TYPE: 'contextType', + CURSOR_VISIBLE: 'cursorVisible', + CTRL_KEY: 'ctrlKey', + CURSOR: 'cursor', + ENGINE_ID: 'engineID', + HEIGHT: 'height', + ID: 'id', + IS_EMOJI: 'isEmoji', + IS_AUTOCORRECT: 'isAutoCorrect', + KEY: 'key', + KEYCODE: 'keyCode', + KEYSET: 'keyset', + KEY_DATA: 'keyData', + LANGUAGE: 'language', + MATCHED_LENGTHS: 'matchedLengths', + MSG: 'msg', + MSG_TYPE: 'type', + OPTION_PREFIX: 'optionPrefix', + OPTION_TYPE: 'optionType', + PAGE_SIZE: 'pageSize', + POSSIBILITIES: 'possibilities', + PROPERTIES: 'properties', + REQUEST_ID: 'requestId', + SHIFT_KEY: 'shiftKey', + SOURCE: 'source', + SOURCES: 'sources', + SPATIAL_DATA: 'spatialData', + SCREEN: 'screen', + STROKES: 'strokes', + TEXT: 'text', + VERTICAL: 'vertical', + VISIBLE: 'visible', + VISIBILITY: 'visibility', + WIDTH: 'width' +}; diff --git a/third_party/google_input_tools/src/chrome/os/message/type.js b/third_party/google_input_tools/src/chrome/os/message/type.js new file mode 100644 index 0000000..24b870c --- /dev/null +++ b/third_party/google_input_tools/src/chrome/os/message/type.js @@ -0,0 +1,57 @@ +// 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.message.Type'); + + +/** + * The message type. + * + * @enum {string} + */ +i18n.input.chrome.message.Type = { + CANDIDATES_BACK: 'candidates_back', + COMMIT_TEXT: 'commit_text', + COMPLETION: 'completion', + CONNECT: 'connect', + CONTEXT_BLUR: 'context_blur', + CONTEXT_FOCUS: 'context_focus', + DATASOURCE_READY: 'datasource_ready', + DISCONNECT: 'disconnect', + DOUBLE_CLICK_ON_SPACE_KEY: 'double_click_on_space_key', + EMOJI_SET_INPUTTOOL: 'emoji_set_inputtool', + EMOJI_UNSET_INPUTTOOL: 'emoji_unset_inputtool', + EXEC_ALL: 'exec_all', + HWT_NETWORK_ERROR: 'hwt_network_error', + HWT_PRIVACY_GOT_IT: 'hwt_privacy_got_it', + HWT_PRIVACY_INFO: 'hwt_privacy_info', + HWT_REQUEST: 'hwt_request', + HWT_SET_INPUTTOOL: 'hwt_set_inputtool', + HWT_UNSET_INPUTTOOL: 'hwt_unset_inputtool', + KEY_CLICK: 'key_click', + KEY_EVENT: 'key_event', + OPTION_CHANGE: 'option_change', + PREDICTION: 'prediction', + SELECT_CANDIDATE: 'select_candidate', + SEND_KEY_EVENT: 'send_key_event', + SET_COMPOSITION: 'set_composition', + SET_LANGUAGE: 'set_language', + SURROUNDING_TEXT_CHANGED: 'surrounding_text_changed', + SWITCH_KEYSET: 'switch_keyset', + TOGGLE_LANGUAGE_STATE: 'toggle_language_state', + UPDATE_SETTINGS: 'update_settings', + VISIBILITY_CHANGE: 'visibility_change' +}; + + + diff --git a/third_party/google_input_tools/third_party/closure_library/AUTHORS b/third_party/google_input_tools/third_party/closure_library/AUTHORS new file mode 100644 index 0000000..c8ad75a --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/AUTHORS @@ -0,0 +1,17 @@ +# This is a list of contributors to the Closure Library. + +# Names should be added to this file like so: +# Name or Organization <email address> + +Google Inc. +Mohamed Mansour <hello@mohamedmansour.com> +Bjorn Tipling <bjorn.tipling@gmail.com> +SameGoal LLC <help@samegoal.com> +Guido Tapia <guido.tapia@gmail.com> +Andrew Mattie <amattie@gmail.com> +Ilia Mirkin <ibmirkin@gmail.com> +Ivan Kozik <ivan.kozik@gmail.com> +Rich Dougherty <rich@rd.gen.nz> +Chad Killingsworth <chadkillingsworth@missouristate.edu> +Dan Pupius <dan.pupius@gmail.com> + diff --git a/third_party/google_input_tools/third_party/closure_library/LICENSE b/third_party/google_input_tools/third_party/closure_library/LICENSE new file mode 100644 index 0000000..04cb0d7 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2013 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/third_party/google_input_tools/third_party/closure_library/README.chromium b/third_party/google_input_tools/third_party/closure_library/README.chromium new file mode 100644 index 0000000..768d8bb --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/README.chromium @@ -0,0 +1,18 @@ +Name: Closure Library +URL: https://github.com/google/closure-library +Version: Unknown +Revision: 946a7d39d4ffe08676c755b21d901e71d9904a3b +InfoURL: http://developers.google.com/closure/library +License: Apache 2.0 +Security Critical: Yes + +Description: +Closure Library is a powerful, low level JavaScript library designed +for building complex and scalable web applications. + +Local Modifications: +Only the files actually used by google-input-tools are kept. This includes +a number of javaScript source files and a few python modules to scan +sources for closure dependencies. Adding missing shebang line on python files +as required. + diff --git a/third_party/google_input_tools/third_party/closure_library/closure/bin/build/closurebuilder.py b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/closurebuilder.py new file mode 100755 index 0000000..1f01e61 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/closurebuilder.py @@ -0,0 +1,274 @@ +#!/usr/bin/env python +# +# Copyright 2009 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Utility for Closure Library dependency calculation. + +ClosureBuilder scans source files to build dependency info. From the +dependencies, the script can produce a manifest in dependency order, +a concatenated script, or compiled output from the Closure Compiler. + +Paths to files can be expressed as individual arguments to the tool (intended +for use with find and xargs). As a convenience, --root can be used to specify +all JS files below a directory. + +usage: %prog [options] [file1.js file2.js ...] +""" + +__author__ = 'nnaze@google.com (Nathan Naze)' + + +import logging +import optparse +import os +import sys + +import depstree +import jscompiler +import source +import treescan + + +def _GetOptionsParser(): + """Get the options parser.""" + + parser = optparse.OptionParser(__doc__) + parser.add_option('-i', + '--input', + dest='inputs', + action='append', + default=[], + help='One or more input files to calculate dependencies ' + 'for. The namespaces in this file will be combined with ' + 'those given with the -n flag to form the set of ' + 'namespaces to find dependencies for.') + parser.add_option('-n', + '--namespace', + dest='namespaces', + action='append', + default=[], + help='One or more namespaces to calculate dependencies ' + 'for. These namespaces will be combined with those given ' + 'with the -i flag to form the set of namespaces to find ' + 'dependencies for. A Closure namespace is a ' + 'dot-delimited path expression declared with a call to ' + 'goog.provide() (e.g. "goog.array" or "foo.bar").') + parser.add_option('--root', + dest='roots', + action='append', + default=[], + help='The paths that should be traversed to build the ' + 'dependencies.') + parser.add_option('-o', + '--output_mode', + dest='output_mode', + type='choice', + action='store', + choices=['list', 'script', 'compiled'], + default='list', + help='The type of output to generate from this script. ' + 'Options are "list" for a list of filenames, "script" ' + 'for a single script containing the contents of all the ' + 'files, or "compiled" to produce compiled output with ' + 'the Closure Compiler. Default is "list".') + parser.add_option('-c', + '--compiler_jar', + dest='compiler_jar', + action='store', + help='The location of the Closure compiler .jar file.') + parser.add_option('-f', + '--compiler_flags', + dest='compiler_flags', + default=[], + action='append', + help='Additional flags to pass to the Closure compiler. ' + 'To pass multiple flags, --compiler_flags has to be ' + 'specified multiple times.') + parser.add_option('-j', + '--jvm_flags', + dest='jvm_flags', + default=[], + action='append', + help='Additional flags to pass to the JVM compiler. ' + 'To pass multiple flags, --jvm_flags has to be ' + 'specified multiple times.') + parser.add_option('--output_file', + dest='output_file', + action='store', + help=('If specified, write output to this path instead of ' + 'writing to standard output.')) + + return parser + + +def _GetInputByPath(path, sources): + """Get the source identified by a path. + + Args: + path: str, A path to a file that identifies a source. + sources: An iterable collection of source objects. + + Returns: + The source from sources identified by path, if found. Converts to + absolute paths for comparison. + """ + for js_source in sources: + # Convert both to absolute paths for comparison. + if os.path.abspath(path) == os.path.abspath(js_source.GetPath()): + return js_source + + +def _GetClosureBaseFile(sources): + """Given a set of sources, returns the one base.js file. + + Note that if zero or two or more base.js files are found, an error message + will be written and the program will be exited. + + Args: + sources: An iterable of _PathSource objects. + + Returns: + The _PathSource representing the base Closure file. + """ + base_files = [ + js_source for js_source in sources if _IsClosureBaseFile(js_source)] + + if not base_files: + logging.error('No Closure base.js file found.') + sys.exit(1) + if len(base_files) > 1: + logging.error('More than one Closure base.js files found at these paths:') + for base_file in base_files: + logging.error(base_file.GetPath()) + sys.exit(1) + return base_files[0] + + +def _IsClosureBaseFile(js_source): + """Returns true if the given _PathSource is the Closure base.js source.""" + return (os.path.basename(js_source.GetPath()) == 'base.js' and + js_source.provides == set(['goog'])) + + +class _PathSource(source.Source): + """Source file subclass that remembers its file path.""" + + def __init__(self, path): + """Initialize a source. + + Args: + path: str, Path to a JavaScript file. The source string will be read + from this file. + """ + super(_PathSource, self).__init__(source.GetFileContents(path)) + + self._path = path + + def __str__(self): + return 'PathSource %s' % self._path + + def GetPath(self): + """Returns the path.""" + return self._path + + +def main(): + logging.basicConfig(format=(sys.argv[0] + ': %(message)s'), + level=logging.INFO) + options, args = _GetOptionsParser().parse_args() + + # Make our output pipe. + if options.output_file: + out = open(options.output_file, 'w') + else: + out = sys.stdout + + sources = set() + + logging.info('Scanning paths...') + for path in options.roots: + for js_path in treescan.ScanTreeForJsFiles(path): + sources.add(_PathSource(js_path)) + + # Add scripts specified on the command line. + for js_path in args: + sources.add(_PathSource(js_path)) + + logging.info('%s sources scanned.', len(sources)) + + # Though deps output doesn't need to query the tree, we still build it + # to validate dependencies. + logging.info('Building dependency tree..') + tree = depstree.DepsTree(sources) + + input_namespaces = set() + inputs = options.inputs or [] + for input_path in inputs: + js_input = _GetInputByPath(input_path, sources) + if not js_input: + logging.error('No source matched input %s', input_path) + sys.exit(1) + input_namespaces.update(js_input.provides) + + input_namespaces.update(options.namespaces) + + if not input_namespaces: + logging.error('No namespaces found. At least one namespace must be ' + 'specified with the --namespace or --input flags.') + sys.exit(2) + + # The Closure Library base file must go first. + base = _GetClosureBaseFile(sources) + deps = [base] + tree.GetDependencies(input_namespaces) + + output_mode = options.output_mode + if output_mode == 'list': + out.writelines([js_source.GetPath() + '\n' for js_source in deps]) + elif output_mode == 'script': + out.writelines([js_source.GetSource() for js_source in deps]) + elif output_mode == 'compiled': + logging.warning("""\ +Closure Compiler now natively understands and orders Closure dependencies and +is prefererred over using this script for performing JavaScript compilation. + +Please migrate your codebase. + +See: +https://code.google.com/p/closure-compiler/wiki/ManageClosureDependencies +""") + + # Make sure a .jar is specified. + if not options.compiler_jar: + logging.error('--compiler_jar flag must be specified if --output is ' + '"compiled"') + sys.exit(2) + + # Will throw an error if the compilation fails. + compiled_source = jscompiler.Compile( + options.compiler_jar, + [js_source.GetPath() for js_source in deps], + jvm_flags=options.jvm_flags, + compiler_flags=options.compiler_flags) + + logging.info('JavaScript compilation succeeded.') + out.write(compiled_source) + + else: + logging.error('Invalid value for --output flag.') + sys.exit(2) + + +if __name__ == '__main__': + main() diff --git a/third_party/google_input_tools/third_party/closure_library/closure/bin/build/depstree.py b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/depstree.py new file mode 100755 index 0000000..418f601 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/depstree.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python +# Copyright 2009 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Class to represent a full Closure Library dependency tree. + +Offers a queryable tree of dependencies of a given set of sources. The tree +will also do logical validation to prevent duplicate provides and circular +dependencies. +""" + +__author__ = 'nnaze@google.com (Nathan Naze)' + + +class DepsTree(object): + """Represents the set of dependencies between source files.""" + + def __init__(self, sources): + """Initializes the tree with a set of sources. + + Args: + sources: A set of JavaScript sources. + + Raises: + MultipleProvideError: A namespace is provided by muplitple sources. + NamespaceNotFoundError: A namespace is required but never provided. + """ + + self._sources = sources + self._provides_map = dict() + + # Ensure nothing was provided twice. + for source in sources: + for provide in source.provides: + if provide in self._provides_map: + raise MultipleProvideError( + provide, [self._provides_map[provide], source]) + + self._provides_map[provide] = source + + # Check that all required namespaces are provided. + for source in sources: + for require in source.requires: + if require not in self._provides_map: + raise NamespaceNotFoundError(require, source) + + def GetDependencies(self, required_namespaces): + """Get source dependencies, in order, for the given namespaces. + + Args: + required_namespaces: A string (for one) or list (for one or more) of + namespaces. + + Returns: + A list of source objects that provide those namespaces and all + requirements, in dependency order. + + Raises: + NamespaceNotFoundError: A namespace is requested but doesn't exist. + CircularDependencyError: A cycle is detected in the dependency tree. + """ + if isinstance(required_namespaces, str): + required_namespaces = [required_namespaces] + + deps_sources = [] + + for namespace in required_namespaces: + for source in DepsTree._ResolveDependencies( + namespace, [], self._provides_map, []): + if source not in deps_sources: + deps_sources.append(source) + + return deps_sources + + @staticmethod + def _ResolveDependencies(required_namespace, deps_list, provides_map, + traversal_path): + """Resolve dependencies for Closure source files. + + Follows the dependency tree down and builds a list of sources in dependency + order. This function will recursively call itself to fill all dependencies + below the requested namespaces, and then append its sources at the end of + the list. + + Args: + required_namespace: String of required namespace. + deps_list: List of sources in dependency order. This function will append + the required source once all of its dependencies are satisfied. + provides_map: Map from namespace to source that provides it. + traversal_path: List of namespaces of our path from the root down the + dependency/recursion tree. Used to identify cyclical dependencies. + This is a list used as a stack -- when the function is entered, the + current namespace is pushed and popped right before returning. + Each recursive call will check that the current namespace does not + appear in the list, throwing a CircularDependencyError if it does. + + Returns: + The given deps_list object filled with sources in dependency order. + + Raises: + NamespaceNotFoundError: A namespace is requested but doesn't exist. + CircularDependencyError: A cycle is detected in the dependency tree. + """ + + source = provides_map.get(required_namespace) + if not source: + raise NamespaceNotFoundError(required_namespace) + + if required_namespace in traversal_path: + traversal_path.append(required_namespace) # do this *after* the test + + # This must be a cycle. + raise CircularDependencyError(traversal_path) + + # If we don't have the source yet, we'll have to visit this namespace and + # add the required dependencies to deps_list. + if source not in deps_list: + traversal_path.append(required_namespace) + + for require in source.requires: + + # Append all other dependencies before we append our own. + DepsTree._ResolveDependencies(require, deps_list, provides_map, + traversal_path) + deps_list.append(source) + + traversal_path.pop() + + return deps_list + + +class BaseDepsTreeError(Exception): + """Base DepsTree error.""" + + def __init__(self): + Exception.__init__(self) + + +class CircularDependencyError(BaseDepsTreeError): + """Raised when a dependency cycle is encountered.""" + + def __init__(self, dependency_list): + BaseDepsTreeError.__init__(self) + self._dependency_list = dependency_list + + def __str__(self): + return ('Encountered circular dependency:\n%s\n' % + '\n'.join(self._dependency_list)) + + +class MultipleProvideError(BaseDepsTreeError): + """Raised when a namespace is provided more than once.""" + + def __init__(self, namespace, sources): + BaseDepsTreeError.__init__(self) + self._namespace = namespace + self._sources = sources + + def __str__(self): + source_strs = map(str, self._sources) + + return ('Namespace "%s" provided more than once in sources:\n%s\n' % + (self._namespace, '\n'.join(source_strs))) + + +class NamespaceNotFoundError(BaseDepsTreeError): + """Raised when a namespace is requested but not provided.""" + + def __init__(self, namespace, source=None): + BaseDepsTreeError.__init__(self) + self._namespace = namespace + self._source = source + + def __str__(self): + msg = 'Namespace "%s" never provided.' % self._namespace + if self._source: + msg += ' Required in %s' % self._source + return msg diff --git a/third_party/google_input_tools/third_party/closure_library/closure/bin/build/depstree_test.py b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/depstree_test.py new file mode 100755 index 0000000..eb4c999 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/depstree_test.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python +# +# Copyright 2009 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Unit test for depstree.""" + +__author__ = 'nnaze@google.com (Nathan Naze)' + + +import unittest + +import depstree + + +def _GetProvides(sources): + """Get all namespaces provided by a collection of sources.""" + + provides = set() + for source in sources: + provides.update(source.provides) + return provides + + +class MockSource(object): + """Mock Source file.""" + + def __init__(self, provides, requires): + self.provides = set(provides) + self.requires = set(requires) + + def __repr__(self): + return 'MockSource %s' % self.provides + + +class DepsTreeTestCase(unittest.TestCase): + """Unit test for DepsTree. Tests several common situations and errors.""" + + def AssertValidDependencies(self, deps_list): + """Validates a dependency list. + + Asserts that a dependency list is valid: For every source in the list, + ensure that every require is provided by a source earlier in the list. + + Args: + deps_list: A list of sources that should be in dependency order. + """ + + for i in range(len(deps_list)): + source = deps_list[i] + previous_provides = _GetProvides(deps_list[:i]) + for require in source.requires: + self.assertTrue( + require in previous_provides, + 'Namespace "%s" not provided before required by %s' % ( + require, source)) + + def testSimpleDepsTree(self): + a = MockSource(['A'], ['B', 'C']) + b = MockSource(['B'], []) + c = MockSource(['C'], ['D']) + d = MockSource(['D'], ['E']) + e = MockSource(['E'], []) + + tree = depstree.DepsTree([a, b, c, d, e]) + + self.AssertValidDependencies(tree.GetDependencies('A')) + self.AssertValidDependencies(tree.GetDependencies('B')) + self.AssertValidDependencies(tree.GetDependencies('C')) + self.AssertValidDependencies(tree.GetDependencies('D')) + self.AssertValidDependencies(tree.GetDependencies('E')) + + def testCircularDependency(self): + # Circular deps + a = MockSource(['A'], ['B']) + b = MockSource(['B'], ['C']) + c = MockSource(['C'], ['A']) + + tree = depstree.DepsTree([a, b, c]) + + self.assertRaises(depstree.CircularDependencyError, + tree.GetDependencies, 'A') + + def testRequiresUndefinedNamespace(self): + a = MockSource(['A'], ['B']) + b = MockSource(['B'], ['C']) + c = MockSource(['C'], ['D']) # But there is no D. + + def MakeDepsTree(): + return depstree.DepsTree([a, b, c]) + + self.assertRaises(depstree.NamespaceNotFoundError, MakeDepsTree) + + def testDepsForMissingNamespace(self): + a = MockSource(['A'], ['B']) + b = MockSource(['B'], []) + + tree = depstree.DepsTree([a, b]) + + # There is no C. + self.assertRaises(depstree.NamespaceNotFoundError, + tree.GetDependencies, 'C') + + def testMultipleRequires(self): + a = MockSource(['A'], ['B']) + b = MockSource(['B'], ['C']) + c = MockSource(['C'], []) + d = MockSource(['D'], ['B']) + + tree = depstree.DepsTree([a, b, c, d]) + self.AssertValidDependencies(tree.GetDependencies(['D', 'A'])) + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/google_input_tools/third_party/closure_library/closure/bin/build/depswriter.py b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/depswriter.py new file mode 100755 index 0000000..dfecc4b --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/depswriter.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python +# +# Copyright 2009 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Generates out a Closure deps.js file given a list of JavaScript sources. + +Paths can be specified as arguments or (more commonly) specifying trees +with the flags (call with --help for descriptions). + +Usage: depswriter.py [path/to/js1.js [path/to/js2.js] ...] +""" + +import logging +import optparse +import os +import posixpath +import shlex +import sys + +import source +import treescan + + +__author__ = 'nnaze@google.com (Nathan Naze)' + + +def MakeDepsFile(source_map): + """Make a generated deps file. + + Args: + source_map: A dict map of the source path to source.Source object. + + Returns: + str, A generated deps file source. + """ + + # Write in path alphabetical order + paths = sorted(source_map.keys()) + + lines = [] + + for path in paths: + js_source = source_map[path] + + # We don't need to add entries that don't provide anything. + if js_source.provides: + lines.append(_GetDepsLine(path, js_source)) + + return ''.join(lines) + + +def _GetDepsLine(path, js_source): + """Get a deps.js file string for a source.""" + + provides = sorted(js_source.provides) + requires = sorted(js_source.requires) + + return 'goog.addDependency(\'%s\', %s, %s);\n' % (path, provides, requires) + + +def _GetOptionsParser(): + """Get the options parser.""" + + parser = optparse.OptionParser(__doc__) + + parser.add_option('--output_file', + dest='output_file', + action='store', + help=('If specified, write output to this path instead of ' + 'writing to standard output.')) + parser.add_option('--root', + dest='roots', + default=[], + action='append', + help='A root directory to scan for JS source files. ' + 'Paths of JS files in generated deps file will be ' + 'relative to this path. This flag may be specified ' + 'multiple times.') + parser.add_option('--root_with_prefix', + dest='roots_with_prefix', + default=[], + action='append', + help='A root directory to scan for JS source files, plus ' + 'a prefix (if either contains a space, surround with ' + 'quotes). Paths in generated deps file will be relative ' + 'to the root, but preceded by the prefix. This flag ' + 'may be specified multiple times.') + parser.add_option('--path_with_depspath', + dest='paths_with_depspath', + default=[], + action='append', + help='A path to a source file and an alternate path to ' + 'the file in the generated deps file (if either contains ' + 'a space, surround with whitespace). This flag may be ' + 'specified multiple times.') + return parser + + +def _NormalizePathSeparators(path): + """Replaces OS-specific path separators with POSIX-style slashes. + + Args: + path: str, A file path. + + Returns: + str, The path with any OS-specific path separators (such as backslash on + Windows) replaced with URL-compatible forward slashes. A no-op on systems + that use POSIX paths. + """ + return path.replace(os.sep, posixpath.sep) + + +def _GetRelativePathToSourceDict(root, prefix=''): + """Scans a top root directory for .js sources. + + Args: + root: str, Root directory. + prefix: str, Prefix for returned paths. + + Returns: + dict, A map of relative paths (with prefix, if given), to source.Source + objects. + """ + # Remember and restore the cwd when we're done. We work from the root so + # that paths are relative from the root. + start_wd = os.getcwd() + os.chdir(root) + + path_to_source = {} + for path in treescan.ScanTreeForJsFiles('.'): + prefixed_path = _NormalizePathSeparators(os.path.join(prefix, path)) + path_to_source[prefixed_path] = source.Source(source.GetFileContents(path)) + + os.chdir(start_wd) + + return path_to_source + + +def _GetPair(s): + """Return a string as a shell-parsed tuple. Two values expected.""" + try: + # shlex uses '\' as an escape character, so they must be escaped. + s = s.replace('\\', '\\\\') + first, second = shlex.split(s) + return (first, second) + except: + raise Exception('Unable to parse input line as a pair: %s' % s) + + +def main(): + """CLI frontend to MakeDepsFile.""" + logging.basicConfig(format=(sys.argv[0] + ': %(message)s'), + level=logging.INFO) + options, args = _GetOptionsParser().parse_args() + + path_to_source = {} + + # Roots without prefixes + for root in options.roots: + path_to_source.update(_GetRelativePathToSourceDict(root)) + + # Roots with prefixes + for root_and_prefix in options.roots_with_prefix: + root, prefix = _GetPair(root_and_prefix) + path_to_source.update(_GetRelativePathToSourceDict(root, prefix=prefix)) + + # Source paths + for path in args: + path_to_source[path] = source.Source(source.GetFileContents(path)) + + # Source paths with alternate deps paths + for path_with_depspath in options.paths_with_depspath: + srcpath, depspath = _GetPair(path_with_depspath) + path_to_source[depspath] = source.Source(source.GetFileContents(srcpath)) + + # Make our output pipe. + if options.output_file: + out = open(options.output_file, 'w') + else: + out = sys.stdout + + out.write('// This file was autogenerated by %s.\n' % sys.argv[0]) + out.write('// Please do not edit.\n') + + out.write(MakeDepsFile(path_to_source)) + + +if __name__ == '__main__': + main() diff --git a/third_party/google_input_tools/third_party/closure_library/closure/bin/build/jscompiler.py b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/jscompiler.py new file mode 100755 index 0000000..783b5d7 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/jscompiler.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# Copyright 2010 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Utility to use the Closure Compiler CLI from Python.""" + + +import logging +import os +import re +import subprocess + + +# Pulls just the major and minor version numbers from the first line of +# 'java -version'. Versions are in the format of [0-9]+\.[0-9]+\..* See: +# http://www.oracle.com/technetwork/java/javase/versioning-naming-139433.html +_VERSION_REGEX = re.compile(r'"([0-9]+)\.([0-9]+)') + + +class JsCompilerError(Exception): + """Raised if there's an error in calling the compiler.""" + pass + + +def _GetJavaVersionString(): + """Get the version string from the Java VM.""" + return subprocess.check_output(['java', '-version'], stderr=subprocess.STDOUT) + + +def _ParseJavaVersion(version_string): + """Returns a 2-tuple for the current version of Java installed. + + Args: + version_string: String of the Java version (e.g. '1.7.2-ea'). + + Returns: + The major and minor versions, as a 2-tuple (e.g. (1, 7)). + """ + match = _VERSION_REGEX.search(version_string) + if match: + version = tuple(int(x, 10) for x in match.groups()) + assert len(version) == 2 + return version + + +def _JavaSupports32BitMode(): + """Determines whether the JVM supports 32-bit mode on the platform.""" + # Suppresses process output to stderr and stdout from showing up in the + # console as we're only trying to determine 32-bit JVM support. + supported = False + try: + devnull = open(os.devnull, 'wb') + return subprocess.call( + ['java', '-d32', '-version'], stdout=devnull, stderr=devnull) == 0 + except IOError: + pass + else: + devnull.close() + return supported + + +def _GetJsCompilerArgs(compiler_jar_path, java_version, source_paths, + jvm_flags, compiler_flags): + """Assembles arguments for call to JsCompiler.""" + + if java_version < (1, 7): + raise JsCompilerError('Closure Compiler requires Java 1.7 or higher. ' + 'Please visit http://www.java.com/getjava') + + args = ['java'] + + # Add JVM flags we believe will produce the best performance. See + # https://groups.google.com/forum/#!topic/closure-library-discuss/7w_O9-vzlj4 + + # Attempt 32-bit mode if available (Java 7 on Mac OS X does not support 32-bit + # mode, for example). + if _JavaSupports32BitMode(): + args += ['-d32'] + + # Prefer the "client" VM. + args += ['-client'] + + # Add JVM flags, if any + if jvm_flags: + args += jvm_flags + + # Add the application JAR. + args += ['-jar', compiler_jar_path] + + for path in source_paths: + args += ['--js', path] + + # Add compiler flags, if any. + if compiler_flags: + args += compiler_flags + + return args + + +def Compile(compiler_jar_path, source_paths, + jvm_flags=None, + compiler_flags=None): + """Prepares command-line call to Closure Compiler. + + Args: + compiler_jar_path: Path to the Closure compiler .jar file. + source_paths: Source paths to build, in order. + jvm_flags: A list of additional flags to pass on to JVM. + compiler_flags: A list of additional flags to pass on to Closure Compiler. + + Returns: + The compiled source, as a string, or None if compilation failed. + """ + + java_version = _ParseJavaVersion(_GetJavaVersionString()) + + args = _GetJsCompilerArgs( + compiler_jar_path, java_version, source_paths, jvm_flags, compiler_flags) + + logging.info('Compiling with the following command: %s', ' '.join(args)) + + try: + return subprocess.check_output(args) + except subprocess.CalledProcessError: + raise JsCompilerError('JavaScript compilation failed.') diff --git a/third_party/google_input_tools/third_party/closure_library/closure/bin/build/source.py b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/source.py new file mode 100755 index 0000000..d4d1dba --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/source.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +# Copyright 2009 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Scans a source JS file for its provided and required namespaces. + +Simple class to scan a JavaScript file and express its dependencies. +""" + +__author__ = 'nnaze@google.com' + + +import re + +_BASE_REGEX_STRING = '^\s*goog\.%s\(\s*[\'"](.+)[\'"]\s*\)' +_PROVIDE_REGEX = re.compile(_BASE_REGEX_STRING % 'provide') +_REQUIRES_REGEX = re.compile(_BASE_REGEX_STRING % 'require') + + +class Source(object): + """Scans a JavaScript source for its provided and required namespaces.""" + + # Matches a "/* ... */" comment. + # Note: We can't definitively distinguish a "/*" in a string literal without a + # state machine tokenizer. We'll assume that a line starting with whitespace + # and "/*" is a comment. + _COMMENT_REGEX = re.compile( + r""" + ^\s* # Start of a new line and whitespace + /\* # Opening "/*" + .*? # Non greedy match of any characters (including newlines) + \*/ # Closing "*/""", + re.MULTILINE | re.DOTALL | re.VERBOSE) + + def __init__(self, source): + """Initialize a source. + + Args: + source: str, The JavaScript source. + """ + + self.provides = set() + self.requires = set() + + self._source = source + self._ScanSource() + + def GetSource(self): + """Get the source as a string.""" + return self._source + + @classmethod + def _StripComments(cls, source): + return cls._COMMENT_REGEX.sub('', source) + + @classmethod + def _HasProvideGoogFlag(cls, source): + """Determines whether the @provideGoog flag is in a comment.""" + for comment_content in cls._COMMENT_REGEX.findall(source): + if '@provideGoog' in comment_content: + return True + + return False + + def _ScanSource(self): + """Fill in provides and requires by scanning the source.""" + + stripped_source = self._StripComments(self.GetSource()) + + source_lines = stripped_source.splitlines() + for line in source_lines: + match = _PROVIDE_REGEX.match(line) + if match: + self.provides.add(match.group(1)) + match = _REQUIRES_REGEX.match(line) + if match: + self.requires.add(match.group(1)) + + # Closure's base file implicitly provides 'goog'. + # This is indicated with the @provideGoog flag. + if self._HasProvideGoogFlag(self.GetSource()): + + if len(self.provides) or len(self.requires): + raise Exception( + 'Base file should not provide or require namespaces.') + + self.provides.add('goog') + + +def GetFileContents(path): + """Get a file's contents as a string. + + Args: + path: str, Path to file. + + Returns: + str, Contents of file. + + Raises: + IOError: An error occurred opening or reading the file. + + """ + fileobj = open(path) + try: + return fileobj.read() + finally: + fileobj.close() diff --git a/third_party/google_input_tools/third_party/closure_library/closure/bin/build/source_test.py b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/source_test.py new file mode 100755 index 0000000..c9e4d12 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/source_test.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python +# +# Copyright 2010 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Unit test for source.""" + +__author__ = 'nnaze@google.com (Nathan Naze)' + + +import unittest + +import source + + +class SourceTestCase(unittest.TestCase): + """Unit test for source. Tests the parser on a known source input.""" + + def testSourceScan(self): + test_source = source.Source(_TEST_SOURCE) + + self.assertEqual(set(['foo', 'foo.test']), + test_source.provides) + self.assertEqual(set(['goog.dom', 'goog.events.EventType']), + test_source.requires) + + def testSourceScanBase(self): + test_source = source.Source(_TEST_BASE_SOURCE) + + self.assertEqual(set(['goog']), + test_source.provides) + self.assertEqual(test_source.requires, set()) + + def testSourceScanBadBase(self): + + def MakeSource(): + source.Source(_TEST_BAD_BASE_SOURCE) + + self.assertRaises(Exception, MakeSource) + + def testStripComments(self): + self.assertEquals( + '\nvar foo = function() {}', + source.Source._StripComments(( + '/* This is\n' + ' a comment split\n' + ' over multiple lines\n' + '*/\n' + 'var foo = function() {}'))) + + def testGoogStatementsInComments(self): + test_source = source.Source(_TEST_COMMENT_SOURCE) + + self.assertEqual(set(['foo']), + test_source.provides) + self.assertEqual(set(['goog.events.EventType']), + test_source.requires) + + def testHasProvideGoog(self): + self.assertTrue(source.Source._HasProvideGoogFlag(_TEST_BASE_SOURCE)) + self.assertTrue(source.Source._HasProvideGoogFlag(_TEST_BAD_BASE_SOURCE)) + self.assertFalse(source.Source._HasProvideGoogFlag(_TEST_COMMENT_SOURCE)) + + +_TEST_SOURCE = """// Fake copyright notice + +/** Very important comment. */ + +goog.provide('foo'); +goog.provide('foo.test'); + +goog.require('goog.dom'); +goog.require('goog.events.EventType'); + +function foo() { + // Set bar to seventeen to increase performance. + this.bar = 17; +} +""" + +_TEST_COMMENT_SOURCE = """// Fake copyright notice + +goog.provide('foo'); + +/* +goog.provide('foo.test'); + */ + +/* +goog.require('goog.dom'); +*/ + +// goog.require('goog.dom'); + +goog.require('goog.events.EventType'); + +function bar() { + this.baz = 55; +} +""" + +_TEST_BASE_SOURCE = """ +/** + * @fileoverview The base file. + * @provideGoog + */ + +var goog = goog || {}; +""" + +_TEST_BAD_BASE_SOURCE = """ +/** + * @fileoverview The base file. + * @provideGoog + */ + +goog.provide('goog'); +""" + + +if __name__ == '__main__': + unittest.main() diff --git a/third_party/google_input_tools/third_party/closure_library/closure/bin/build/treescan.py b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/treescan.py new file mode 100755 index 0000000..6694593 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/bin/build/treescan.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# +# Copyright 2010 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Shared utility functions for scanning directory trees.""" + +import os +import re + + +__author__ = 'nnaze@google.com (Nathan Naze)' + + +# Matches a .js file path. +_JS_FILE_REGEX = re.compile(r'^.+\.js$') + + +def ScanTreeForJsFiles(root): + """Scans a directory tree for JavaScript files. + + Args: + root: str, Path to a root directory. + + Returns: + An iterable of paths to JS files, relative to cwd. + """ + return ScanTree(root, path_filter=_JS_FILE_REGEX) + + +def ScanTree(root, path_filter=None, ignore_hidden=True): + """Scans a directory tree for files. + + Args: + root: str, Path to a root directory. + path_filter: A regular expression filter. If set, only paths matching + the path_filter are returned. + ignore_hidden: If True, do not follow or return hidden directories or files + (those starting with a '.' character). + + Yields: + A string path to files, relative to cwd. + """ + + def OnError(os_error): + raise os_error + + for dirpath, dirnames, filenames in os.walk(root, onerror=OnError): + # os.walk allows us to modify dirnames to prevent decent into particular + # directories. Avoid hidden directories. + for dirname in dirnames: + if ignore_hidden and dirname.startswith('.'): + dirnames.remove(dirname) + + for filename in filenames: + + # nothing that starts with '.' + if ignore_hidden and filename.startswith('.'): + continue + + fullpath = os.path.join(dirpath, filename) + + if path_filter and not path_filter.match(fullpath): + continue + + yield os.path.normpath(fullpath) diff --git a/third_party/google_input_tools/third_party/closure_library/closure/bin/calcdeps.py b/third_party/google_input_tools/third_party/closure_library/closure/bin/calcdeps.py new file mode 100755 index 0000000..9cb1a6d --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/bin/calcdeps.py @@ -0,0 +1,590 @@ +#!/usr/bin/env python +# +# Copyright 2006 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Calculates JavaScript dependencies without requiring Google's build system. + +This tool is deprecated and is provided for legacy users. +See build/closurebuilder.py and build/depswriter.py for the current tools. + +It iterates over a number of search paths and builds a dependency tree. With +the inputs provided, it walks the dependency tree and outputs all the files +required for compilation. +""" + + + + + +try: + import distutils.version +except ImportError: + # distutils is not available in all environments + distutils = None + +import logging +import optparse +import os +import re +import subprocess +import sys + + +_BASE_REGEX_STRING = '^\s*goog\.%s\(\s*[\'"](.+)[\'"]\s*\)' +req_regex = re.compile(_BASE_REGEX_STRING % 'require') +prov_regex = re.compile(_BASE_REGEX_STRING % 'provide') +ns_regex = re.compile('^ns:((\w+\.)*(\w+))$') +version_regex = re.compile('[\.0-9]+') + + +def IsValidFile(ref): + """Returns true if the provided reference is a file and exists.""" + return os.path.isfile(ref) + + +def IsJsFile(ref): + """Returns true if the provided reference is a Javascript file.""" + return ref.endswith('.js') + + +def IsNamespace(ref): + """Returns true if the provided reference is a namespace.""" + return re.match(ns_regex, ref) is not None + + +def IsDirectory(ref): + """Returns true if the provided reference is a directory.""" + return os.path.isdir(ref) + + +def ExpandDirectories(refs): + """Expands any directory references into inputs. + + Description: + Looks for any directories in the provided references. Found directories + are recursively searched for .js files, which are then added to the result + list. + + Args: + refs: a list of references such as files, directories, and namespaces + + Returns: + A list of references with directories removed and replaced by any + .js files that are found in them. Also, the paths will be normalized. + """ + result = [] + for ref in refs: + if IsDirectory(ref): + # Disable 'Unused variable' for subdirs + # pylint: disable=unused-variable + for (directory, subdirs, filenames) in os.walk(ref): + for filename in filenames: + if IsJsFile(filename): + result.append(os.path.join(directory, filename)) + else: + result.append(ref) + return map(os.path.normpath, result) + + +class DependencyInfo(object): + """Represents a dependency that is used to build and walk a tree.""" + + def __init__(self, filename): + self.filename = filename + self.provides = [] + self.requires = [] + + def __str__(self): + return '%s Provides: %s Requires: %s' % (self.filename, + repr(self.provides), + repr(self.requires)) + + +def BuildDependenciesFromFiles(files): + """Build a list of dependencies from a list of files. + + Description: + Takes a list of files, extracts their provides and requires, and builds + out a list of dependency objects. + + Args: + files: a list of files to be parsed for goog.provides and goog.requires. + + Returns: + A list of dependency objects, one for each file in the files argument. + """ + result = [] + filenames = set() + for filename in files: + if filename in filenames: + continue + + # Python 3 requires the file encoding to be specified + if (sys.version_info[0] < 3): + file_handle = open(filename, 'r') + else: + file_handle = open(filename, 'r', encoding='utf8') + + try: + dep = CreateDependencyInfo(filename, file_handle) + result.append(dep) + finally: + file_handle.close() + + filenames.add(filename) + + return result + + +def CreateDependencyInfo(filename, source): + """Create dependency info. + + Args: + filename: Filename for source. + source: File-like object containing source. + + Returns: + A DependencyInfo object with provides and requires filled. + """ + dep = DependencyInfo(filename) + for line in source: + if re.match(req_regex, line): + dep.requires.append(re.search(req_regex, line).group(1)) + if re.match(prov_regex, line): + dep.provides.append(re.search(prov_regex, line).group(1)) + return dep + + +def BuildDependencyHashFromDependencies(deps): + """Builds a hash for searching dependencies by the namespaces they provide. + + Description: + Dependency objects can provide multiple namespaces. This method enumerates + the provides of each dependency and adds them to a hash that can be used + to easily resolve a given dependency by a namespace it provides. + + Args: + deps: a list of dependency objects used to build the hash. + + Raises: + Exception: If a multiple files try to provide the same namepace. + + Returns: + A hash table { namespace: dependency } that can be used to resolve a + dependency by a namespace it provides. + """ + dep_hash = {} + for dep in deps: + for provide in dep.provides: + if provide in dep_hash: + raise Exception('Duplicate provide (%s) in (%s, %s)' % ( + provide, + dep_hash[provide].filename, + dep.filename)) + dep_hash[provide] = dep + return dep_hash + + +def CalculateDependencies(paths, inputs): + """Calculates the dependencies for given inputs. + + Description: + This method takes a list of paths (files, directories) and builds a + searchable data structure based on the namespaces that each .js file + provides. It then parses through each input, resolving dependencies + against this data structure. The final output is a list of files, + including the inputs, that represent all of the code that is needed to + compile the given inputs. + + Args: + paths: the references (files, directories) that are used to build the + dependency hash. + inputs: the inputs (files, directories, namespaces) that have dependencies + that need to be calculated. + + Raises: + Exception: if a provided input is invalid. + + Returns: + A list of all files, including inputs, that are needed to compile the given + inputs. + """ + deps = BuildDependenciesFromFiles(paths + inputs) + search_hash = BuildDependencyHashFromDependencies(deps) + result_list = [] + seen_list = [] + for input_file in inputs: + if IsNamespace(input_file): + namespace = re.search(ns_regex, input_file).group(1) + if namespace not in search_hash: + raise Exception('Invalid namespace (%s)' % namespace) + input_file = search_hash[namespace].filename + if not IsValidFile(input_file) or not IsJsFile(input_file): + raise Exception('Invalid file (%s)' % input_file) + seen_list.append(input_file) + file_handle = open(input_file, 'r') + try: + for line in file_handle: + if re.match(req_regex, line): + require = re.search(req_regex, line).group(1) + ResolveDependencies(require, search_hash, result_list, seen_list) + finally: + file_handle.close() + result_list.append(input_file) + + # All files depend on base.js, so put it first. + base_js_path = FindClosureBasePath(paths) + if base_js_path: + result_list.insert(0, base_js_path) + else: + logging.warning('Closure Library base.js not found.') + + return result_list + + +def FindClosureBasePath(paths): + """Given a list of file paths, return Closure base.js path, if any. + + Args: + paths: A list of paths. + + Returns: + The path to Closure's base.js file including filename, if found. + """ + + for path in paths: + pathname, filename = os.path.split(path) + + if filename == 'base.js': + f = open(path) + + is_base = False + + # Sanity check that this is the Closure base file. Check that this + # is where goog is defined. This is determined by the @provideGoog + # flag. + for line in f: + if '@provideGoog' in line: + is_base = True + break + + f.close() + + if is_base: + return path + +def ResolveDependencies(require, search_hash, result_list, seen_list): + """Takes a given requirement and resolves all of the dependencies for it. + + Description: + A given requirement may require other dependencies. This method + recursively resolves all dependencies for the given requirement. + + Raises: + Exception: when require does not exist in the search_hash. + + Args: + require: the namespace to resolve dependencies for. + search_hash: the data structure used for resolving dependencies. + result_list: a list of filenames that have been calculated as dependencies. + This variable is the output for this function. + seen_list: a list of filenames that have been 'seen'. This is required + for the dependency->dependant ordering. + """ + if require not in search_hash: + raise Exception('Missing provider for (%s)' % require) + + dep = search_hash[require] + if not dep.filename in seen_list: + seen_list.append(dep.filename) + for sub_require in dep.requires: + ResolveDependencies(sub_require, search_hash, result_list, seen_list) + result_list.append(dep.filename) + + +def GetDepsLine(dep, base_path): + """Returns a JS string for a dependency statement in the deps.js file. + + Args: + dep: The dependency that we're printing. + base_path: The path to Closure's base.js including filename. + """ + return 'goog.addDependency("%s", %s, %s);' % ( + GetRelpath(dep.filename, base_path), dep.provides, dep.requires) + + +def GetRelpath(path, start): + """Return a relative path to |path| from |start|.""" + # NOTE: Python 2.6 provides os.path.relpath, which has almost the same + # functionality as this function. Since we want to support 2.4, we have + # to implement it manually. :( + path_list = os.path.abspath(os.path.normpath(path)).split(os.sep) + start_list = os.path.abspath( + os.path.normpath(os.path.dirname(start))).split(os.sep) + + common_prefix_count = 0 + for i in range(0, min(len(path_list), len(start_list))): + if path_list[i] != start_list[i]: + break + common_prefix_count += 1 + + # Always use forward slashes, because this will get expanded to a url, + # not a file path. + return '/'.join(['..'] * (len(start_list) - common_prefix_count) + + path_list[common_prefix_count:]) + + +def PrintLine(msg, out): + out.write(msg) + out.write('\n') + + +def PrintDeps(source_paths, deps, out): + """Print out a deps.js file from a list of source paths. + + Args: + source_paths: Paths that we should generate dependency info for. + deps: Paths that provide dependency info. Their dependency info should + not appear in the deps file. + out: The output file. + + Returns: + True on success, false if it was unable to find the base path + to generate deps relative to. + """ + base_path = FindClosureBasePath(source_paths + deps) + if not base_path: + return False + + PrintLine('// This file was autogenerated by calcdeps.py', out) + excludesSet = set(deps) + + for dep in BuildDependenciesFromFiles(source_paths + deps): + if not dep.filename in excludesSet: + PrintLine(GetDepsLine(dep, base_path), out) + + return True + + +def PrintScript(source_paths, out): + for index, dep in enumerate(source_paths): + PrintLine('// Input %d' % index, out) + f = open(dep, 'r') + PrintLine(f.read(), out) + f.close() + + +def GetJavaVersion(): + """Returns the string for the current version of Java installed.""" + proc = subprocess.Popen(['java', '-version'], stderr=subprocess.PIPE) + proc.wait() + version_line = proc.stderr.read().splitlines()[0] + return version_regex.search(version_line).group() + + +def FilterByExcludes(options, files): + """Filters the given files by the exlusions specified at the command line. + + Args: + options: The flags to calcdeps. + files: The files to filter. + Returns: + A list of files. + """ + excludes = [] + if options.excludes: + excludes = ExpandDirectories(options.excludes) + + excludesSet = set(excludes) + return [i for i in files if not i in excludesSet] + + +def GetPathsFromOptions(options): + """Generates the path files from flag options. + + Args: + options: The flags to calcdeps. + Returns: + A list of files in the specified paths. (strings). + """ + + search_paths = options.paths + if not search_paths: + search_paths = ['.'] # Add default folder if no path is specified. + + search_paths = ExpandDirectories(search_paths) + return FilterByExcludes(options, search_paths) + + +def GetInputsFromOptions(options): + """Generates the inputs from flag options. + + Args: + options: The flags to calcdeps. + Returns: + A list of inputs (strings). + """ + inputs = options.inputs + if not inputs: # Parse stdin + logging.info('No inputs specified. Reading from stdin...') + inputs = filter(None, [line.strip('\n') for line in sys.stdin.readlines()]) + + logging.info('Scanning files...') + inputs = ExpandDirectories(inputs) + + return FilterByExcludes(options, inputs) + + +def Compile(compiler_jar_path, source_paths, out, flags=None): + """Prepares command-line call to Closure compiler. + + Args: + compiler_jar_path: Path to the Closure compiler .jar file. + source_paths: Source paths to build, in order. + flags: A list of additional flags to pass on to Closure compiler. + """ + args = ['java', '-jar', compiler_jar_path] + for path in source_paths: + args += ['--js', path] + + if flags: + args += flags + + logging.info('Compiling with the following command: %s', ' '.join(args)) + proc = subprocess.Popen(args, stdout=subprocess.PIPE) + (stdoutdata, stderrdata) = proc.communicate() + if proc.returncode != 0: + logging.error('JavaScript compilation failed.') + sys.exit(1) + else: + out.write(stdoutdata) + + +def main(): + """The entrypoint for this script.""" + + logging.basicConfig(format='calcdeps.py: %(message)s', level=logging.INFO) + + usage = 'usage: %prog [options] arg' + parser = optparse.OptionParser(usage) + parser.add_option('-i', + '--input', + dest='inputs', + action='append', + help='The inputs to calculate dependencies for. Valid ' + 'values can be files, directories, or namespaces ' + '(ns:goog.net.XhrIo). Only relevant to "list" and ' + '"script" output.') + parser.add_option('-p', + '--path', + dest='paths', + action='append', + help='The paths that should be traversed to build the ' + 'dependencies.') + parser.add_option('-d', + '--dep', + dest='deps', + action='append', + help='Directories or files that should be traversed to ' + 'find required dependencies for the deps file. ' + 'Does not generate dependency information for names ' + 'provided by these files. Only useful in "deps" mode.') + parser.add_option('-e', + '--exclude', + dest='excludes', + action='append', + help='Files or directories to exclude from the --path ' + 'and --input flags') + parser.add_option('-o', + '--output_mode', + dest='output_mode', + action='store', + default='list', + help='The type of output to generate from this script. ' + 'Options are "list" for a list of filenames, "script" ' + 'for a single script containing the contents of all the ' + 'file, "deps" to generate a deps.js file for all ' + 'paths, or "compiled" to produce compiled output with ' + 'the Closure compiler.') + parser.add_option('-c', + '--compiler_jar', + dest='compiler_jar', + action='store', + help='The location of the Closure compiler .jar file.') + parser.add_option('-f', + '--compiler_flag', + '--compiler_flags', # for backwards compatability + dest='compiler_flags', + action='append', + help='Additional flag to pass to the Closure compiler. ' + 'May be specified multiple times to pass multiple flags.') + parser.add_option('--output_file', + dest='output_file', + action='store', + help=('If specified, write output to this path instead of ' + 'writing to standard output.')) + + (options, args) = parser.parse_args() + + search_paths = GetPathsFromOptions(options) + + if options.output_file: + out = open(options.output_file, 'w') + else: + out = sys.stdout + + if options.output_mode == 'deps': + result = PrintDeps(search_paths, ExpandDirectories(options.deps or []), out) + if not result: + logging.error('Could not find Closure Library in the specified paths') + sys.exit(1) + + return + + inputs = GetInputsFromOptions(options) + + logging.info('Finding Closure dependencies...') + deps = CalculateDependencies(search_paths, inputs) + output_mode = options.output_mode + + if output_mode == 'script': + PrintScript(deps, out) + elif output_mode == 'list': + # Just print out a dep per line + for dep in deps: + PrintLine(dep, out) + elif output_mode == 'compiled': + # Make sure a .jar is specified. + if not options.compiler_jar: + logging.error('--compiler_jar flag must be specified if --output is ' + '"compiled"') + sys.exit(1) + + # User friendly version check. + if distutils and not (distutils.version.LooseVersion(GetJavaVersion()) > + distutils.version.LooseVersion('1.6')): + logging.error('Closure Compiler requires Java 1.6 or higher.') + logging.error('Please visit http://www.java.com/getjava') + sys.exit(1) + + Compile(options.compiler_jar, deps, out, options.compiler_flags) + + else: + logging.error('Invalid value for --output flag.') + sys.exit(1) + +if __name__ == '__main__': + main() diff --git a/third_party/google_input_tools/third_party/closure_library/closure/bin/scopify.py b/third_party/google_input_tools/third_party/closure_library/closure/bin/scopify.py new file mode 100755 index 0000000..d8057ef --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/bin/scopify.py @@ -0,0 +1,221 @@ +#!/usr/bin/python +# +# Copyright 2010 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Automatically converts codebases over to goog.scope. + +Usage: +cd path/to/my/dir; +../../../../javascript/closure/bin/scopify.py + +Scans every file in this directory, recursively. Looks for existing +goog.scope calls, and goog.require'd symbols. If it makes sense to +generate a goog.scope call for the file, then we will do so, and +try to auto-generate some aliases based on the goog.require'd symbols. + +Known Issues: + + When a file is goog.scope'd, the file contents will be indented +2. + This may put some lines over 80 chars. These will need to be fixed manually. + + We will only try to create aliases for capitalized names. We do not check + to see if those names will conflict with any existing locals. + + This creates merge conflicts for every line of every outstanding change. + If you intend to run this on your codebase, make sure your team members + know. Better yet, send them this script so that they can scopify their + outstanding changes and "accept theirs". + + When an alias is "captured", it can no longer be stubbed out for testing. + Run your tests. + +""" + +__author__ = 'nicksantos@google.com (Nick Santos)' + +import os.path +import re +import sys + +REQUIRES_RE = re.compile(r"goog.require\('([^']*)'\)") + +# Edit this manually if you want something to "always" be aliased. +# TODO(nicksantos): Add a flag for this. +DEFAULT_ALIASES = {} + +def Transform(lines): + """Converts the contents of a file into javascript that uses goog.scope. + + Arguments: + lines: A list of strings, corresponding to each line of the file. + Returns: + A new list of strings, or None if the file was not modified. + """ + requires = [] + + # Do an initial scan to be sure that this file can be processed. + for line in lines: + # Skip this file if it has already been scopified. + if line.find('goog.scope') != -1: + return None + + # If there are any global vars or functions, then we also have + # to skip the whole file. We might be able to deal with this + # more elegantly. + if line.find('var ') == 0 or line.find('function ') == 0: + return None + + for match in REQUIRES_RE.finditer(line): + requires.append(match.group(1)) + + if len(requires) == 0: + return None + + # Backwards-sort the requires, so that when one is a substring of another, + # we match the longer one first. + for val in DEFAULT_ALIASES.values(): + if requires.count(val) == 0: + requires.append(val) + + requires.sort() + requires.reverse() + + # Generate a map of requires to their aliases + aliases_to_globals = DEFAULT_ALIASES.copy() + for req in requires: + index = req.rfind('.') + if index == -1: + alias = req + else: + alias = req[(index + 1):] + + # Don't scopify lowercase namespaces, because they may conflict with + # local variables. + if alias[0].isupper(): + aliases_to_globals[alias] = req + + aliases_to_matchers = {} + globals_to_aliases = {} + for alias, symbol in aliases_to_globals.items(): + globals_to_aliases[symbol] = alias + aliases_to_matchers[alias] = re.compile('\\b%s\\b' % symbol) + + # Insert a goog.scope that aliases all required symbols. + result = [] + + START = 0 + SEEN_REQUIRES = 1 + IN_SCOPE = 2 + + mode = START + aliases_used = set() + insertion_index = None + num_blank_lines = 0 + for line in lines: + if mode == START: + result.append(line) + + if re.search(REQUIRES_RE, line): + mode = SEEN_REQUIRES + + elif mode == SEEN_REQUIRES: + if (line and + not re.search(REQUIRES_RE, line) and + not line.isspace()): + # There should be two blank lines before goog.scope + result += ['\n'] * 2 + result.append('goog.scope(function() {\n') + insertion_index = len(result) + result += ['\n'] * num_blank_lines + mode = IN_SCOPE + elif line.isspace(): + # Keep track of the number of blank lines before each block of code so + # that we can move them after the goog.scope line if necessary. + num_blank_lines += 1 + else: + # Print the blank lines we saw before this code block + result += ['\n'] * num_blank_lines + num_blank_lines = 0 + result.append(line) + + if mode == IN_SCOPE: + for symbol in requires: + if not symbol in globals_to_aliases: + continue + + alias = globals_to_aliases[symbol] + matcher = aliases_to_matchers[alias] + for match in matcher.finditer(line): + # Check to make sure we're not in a string. + # We do this by being as conservative as possible: + # if there are any quote or double quote characters + # before the symbol on this line, then bail out. + before_symbol = line[:match.start(0)] + if before_symbol.count('"') > 0 or before_symbol.count("'") > 0: + continue + + line = line.replace(match.group(0), alias) + aliases_used.add(alias) + + if line.isspace(): + # Truncate all-whitespace lines + result.append('\n') + else: + result.append(line) + + if len(aliases_used): + aliases_used = [alias for alias in aliases_used] + aliases_used.sort() + aliases_used.reverse() + for alias in aliases_used: + symbol = aliases_to_globals[alias] + result.insert(insertion_index, + 'var %s = %s;\n' % (alias, symbol)) + result.append('}); // goog.scope\n') + return result + else: + return None + +def TransformFileAt(path): + """Converts a file into javascript that uses goog.scope. + + Arguments: + path: A path to a file. + """ + f = open(path) + lines = Transform(f.readlines()) + if lines: + f = open(path, 'w') + for l in lines: + f.write(l) + f.close() + +if __name__ == '__main__': + args = sys.argv[1:] + if not len(args): + args = '.' + + for file_name in args: + if os.path.isdir(file_name): + for root, dirs, files in os.walk(file_name): + for name in files: + if name.endswith('.js') and \ + not os.path.islink(os.path.join(root, name)): + TransformFileAt(os.path.join(root, name)) + else: + if file_name.endswith('.js') and \ + not os.path.islink(file_name): + TransformFileAt(file_name) diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/announcer.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/announcer.js new file mode 100644 index 0000000..ee87199 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/announcer.js @@ -0,0 +1,111 @@ +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/** + * @fileoverview Announcer that allows messages to be spoken by assistive + * technologies. + */ + +goog.provide('goog.a11y.aria.Announcer'); + +goog.require('goog.Disposable'); +goog.require('goog.a11y.aria'); +goog.require('goog.a11y.aria.LivePriority'); +goog.require('goog.a11y.aria.State'); +goog.require('goog.dom'); +goog.require('goog.object'); + + + +/** + * Class that allows messages to be spoken by assistive technologies that the + * user may have active. + * + * @param {goog.dom.DomHelper=} opt_domHelper DOM helper. + * @constructor + * @extends {goog.Disposable} + * @final + */ +goog.a11y.aria.Announcer = function(opt_domHelper) { + goog.a11y.aria.Announcer.base(this, 'constructor'); + + /** + * @type {goog.dom.DomHelper} + * @private + */ + this.domHelper_ = opt_domHelper || goog.dom.getDomHelper(); + + /** + * Map of priority to live region elements to use for communicating updates. + * Elements are created on demand. + * @type {Object.<goog.a11y.aria.LivePriority, !Element>} + * @private + */ + this.liveRegions_ = {}; +}; +goog.inherits(goog.a11y.aria.Announcer, goog.Disposable); + + +/** @override */ +goog.a11y.aria.Announcer.prototype.disposeInternal = function() { + goog.object.forEach( + this.liveRegions_, this.domHelper_.removeNode, this.domHelper_); + this.liveRegions_ = null; + this.domHelper_ = null; + goog.a11y.aria.Announcer.base(this, 'disposeInternal'); +}; + + +/** + * Announce a message to be read by any assistive technologies the user may + * have active. + * @param {string} message The message to announce to screen readers. + * @param {goog.a11y.aria.LivePriority=} opt_priority The priority of the + * message. Defaults to POLITE. + */ +goog.a11y.aria.Announcer.prototype.say = function(message, opt_priority) { + goog.dom.setTextContent(this.getLiveRegion_( + opt_priority || goog.a11y.aria.LivePriority.POLITE), message); +}; + + +/** + * Returns an aria-live region that can be used to communicate announcements. + * @param {!goog.a11y.aria.LivePriority} priority The required priority. + * @return {!Element} A live region of the requested priority. + * @private + */ +goog.a11y.aria.Announcer.prototype.getLiveRegion_ = function(priority) { + var liveRegion = this.liveRegions_[priority]; + if (liveRegion) { + // Make sure the live region is not aria-hidden. + goog.a11y.aria.removeState(liveRegion, goog.a11y.aria.State.HIDDEN); + return liveRegion; + } + liveRegion = this.domHelper_.createElement('div'); + // Note that IE has a habit of declaring things that aren't display:none as + // invisible to third-party tools like JAWs, so we can't just use height:0. + liveRegion.style.position = 'absolute'; + liveRegion.style.top = '-1000px'; + liveRegion.style.height = '1px'; + liveRegion.style.overflow = 'hidden'; + goog.a11y.aria.setState(liveRegion, goog.a11y.aria.State.LIVE, + priority); + goog.a11y.aria.setState(liveRegion, goog.a11y.aria.State.ATOMIC, + 'true'); + this.domHelper_.getDocument().body.appendChild(liveRegion); + this.liveRegions_[priority] = liveRegion; + return liveRegion; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/aria.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/aria.js new file mode 100644 index 0000000..2774529 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/aria.js @@ -0,0 +1,364 @@ +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/** + * @fileoverview Utilities for adding, removing and setting ARIA roles and + * states as defined by W3C ARIA standard: http://www.w3.org/TR/wai-aria/ + * All modern browsers have some form of ARIA support, so no browser checks are + * performed when adding ARIA to components. + * + */ + +goog.provide('goog.a11y.aria'); + +goog.require('goog.a11y.aria.Role'); +goog.require('goog.a11y.aria.State'); +goog.require('goog.a11y.aria.datatables'); +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.object'); + + +/** + * ARIA states/properties prefix. + * @private + */ +goog.a11y.aria.ARIA_PREFIX_ = 'aria-'; + + +/** + * ARIA role attribute. + * @private + */ +goog.a11y.aria.ROLE_ATTRIBUTE_ = 'role'; + + +/** + * A list of tag names for which we don't need to set ARIA role and states + * because they have well supported semantics for screen readers or because + * they don't contain content to be made accessible. + * @private + */ +goog.a11y.aria.TAGS_WITH_ASSUMED_ROLES_ = [ + goog.dom.TagName.A, + goog.dom.TagName.AREA, + goog.dom.TagName.BUTTON, + goog.dom.TagName.HEAD, + goog.dom.TagName.INPUT, + goog.dom.TagName.LINK, + goog.dom.TagName.MENU, + goog.dom.TagName.META, + goog.dom.TagName.OPTGROUP, + goog.dom.TagName.OPTION, + goog.dom.TagName.PROGRESS, + goog.dom.TagName.STYLE, + goog.dom.TagName.SELECT, + goog.dom.TagName.SOURCE, + goog.dom.TagName.TEXTAREA, + goog.dom.TagName.TITLE, + goog.dom.TagName.TRACK +]; + + +/** + * Sets the role of an element. If the roleName is + * empty string or null, the role for the element is removed. + * We encourage clients to call the goog.a11y.aria.removeRole + * method instead of setting null and empty string values. + * Special handling for this case is added to ensure + * backword compatibility with existing code. + * + * @param {!Element} element DOM node to set role of. + * @param {!goog.a11y.aria.Role|string} roleName role name(s). + */ +goog.a11y.aria.setRole = function(element, roleName) { + if (!roleName) { + // Setting the ARIA role to empty string is not allowed + // by the ARIA standard. + goog.a11y.aria.removeRole(element); + } else { + if (goog.asserts.ENABLE_ASSERTS) { + goog.asserts.assert(goog.object.containsValue( + goog.a11y.aria.Role, roleName), 'No such ARIA role ' + roleName); + } + element.setAttribute(goog.a11y.aria.ROLE_ATTRIBUTE_, roleName); + } +}; + + +/** + * Gets role of an element. + * @param {!Element} element DOM element to get role of. + * @return {!goog.a11y.aria.Role} ARIA Role name. + */ +goog.a11y.aria.getRole = function(element) { + var role = element.getAttribute(goog.a11y.aria.ROLE_ATTRIBUTE_); + return /** @type {goog.a11y.aria.Role} */ (role) || null; +}; + + +/** + * Removes role of an element. + * @param {!Element} element DOM element to remove the role from. + */ +goog.a11y.aria.removeRole = function(element) { + element.removeAttribute(goog.a11y.aria.ROLE_ATTRIBUTE_); +}; + + +/** + * Sets the state or property of an element. + * @param {!Element} element DOM node where we set state. + * @param {!(goog.a11y.aria.State|string)} stateName State attribute being set. + * Automatically adds prefix 'aria-' to the state name if the attribute is + * not an extra attribute. + * @param {string|boolean|number|!goog.array.ArrayLike.<string>} value Value + * for the state attribute. + */ +goog.a11y.aria.setState = function(element, stateName, value) { + if (goog.isArrayLike(value)) { + var array = /** @type {!goog.array.ArrayLike.<string>} */ (value); + value = array.join(' '); + } + var attrStateName = goog.a11y.aria.getAriaAttributeName_(stateName); + if (value === '' || value == undefined) { + var defaultValueMap = goog.a11y.aria.datatables.getDefaultValuesMap(); + // Work around for browsers that don't properly support ARIA. + // According to the ARIA W3C standard, user agents should allow + // setting empty value which results in setting the default value + // for the ARIA state if such exists. The exact text from the ARIA W3C + // standard (http://www.w3.org/TR/wai-aria/states_and_properties): + // "When a value is indicated as the default, the user agent + // MUST follow the behavior prescribed by this value when the state or + // property is empty or undefined." + // The defaultValueMap contains the default values for the ARIA states + // and has as a key the goog.a11y.aria.State constant for the state. + if (stateName in defaultValueMap) { + element.setAttribute(attrStateName, defaultValueMap[stateName]); + } else { + element.removeAttribute(attrStateName); + } + } else { + element.setAttribute(attrStateName, value); + } +}; + + +/** + * Remove the state or property for the element. + * @param {!Element} element DOM node where we set state. + * @param {!goog.a11y.aria.State} stateName State name. + */ +goog.a11y.aria.removeState = function(element, stateName) { + element.removeAttribute(goog.a11y.aria.getAriaAttributeName_(stateName)); +}; + + +/** + * Gets value of specified state or property. + * @param {!Element} element DOM node to get state from. + * @param {!goog.a11y.aria.State|string} stateName State name. + * @return {string} Value of the state attribute. + */ +goog.a11y.aria.getState = function(element, stateName) { + // TODO(user): return properly typed value result -- + // boolean, number, string, null. We should be able to chain + // getState(...) and setState(...) methods. + + var attr = + /** @type {string|number|boolean} */ (element.getAttribute( + goog.a11y.aria.getAriaAttributeName_(stateName))); + var isNullOrUndefined = attr == null || attr == undefined; + return isNullOrUndefined ? '' : String(attr); +}; + + +/** + * Returns the activedescendant element for the input element by + * using the activedescendant ARIA property of the given element. + * @param {!Element} element DOM node to get activedescendant + * element for. + * @return {?Element} DOM node of the activedescendant, if found. + */ +goog.a11y.aria.getActiveDescendant = function(element) { + var id = goog.a11y.aria.getState( + element, goog.a11y.aria.State.ACTIVEDESCENDANT); + return goog.dom.getOwnerDocument(element).getElementById(id); +}; + + +/** + * Sets the activedescendant ARIA property value for an element. + * If the activeElement is not null, it should have an id set. + * @param {!Element} element DOM node to set activedescendant ARIA property to. + * @param {?Element} activeElement DOM node being set as activedescendant. + */ +goog.a11y.aria.setActiveDescendant = function(element, activeElement) { + var id = ''; + if (activeElement) { + id = activeElement.id; + goog.asserts.assert(id, 'The active element should have an id.'); + } + + goog.a11y.aria.setState(element, goog.a11y.aria.State.ACTIVEDESCENDANT, id); +}; + + +/** + * Gets the label of the given element. + * @param {!Element} element DOM node to get label from. + * @return {string} label The label. + */ +goog.a11y.aria.getLabel = function(element) { + return goog.a11y.aria.getState(element, goog.a11y.aria.State.LABEL); +}; + + +/** + * Sets the label of the given element. + * @param {!Element} element DOM node to set label to. + * @param {string} label The label to set. + */ +goog.a11y.aria.setLabel = function(element, label) { + goog.a11y.aria.setState(element, goog.a11y.aria.State.LABEL, label); +}; + + +/** + * Asserts that the element has a role set if it's not an HTML element whose + * semantics is well supported by most screen readers. + * Only to be used internally by the ARIA library in goog.a11y.aria.*. + * @param {!Element} element The element to assert an ARIA role set. + * @param {!goog.array.ArrayLike.<string>} allowedRoles The child roles of + * the roles. + */ +goog.a11y.aria.assertRoleIsSetInternalUtil = function(element, allowedRoles) { + if (goog.array.contains(goog.a11y.aria.TAGS_WITH_ASSUMED_ROLES_, + element.tagName)) { + return; + } + var elementRole = /** @type {string}*/ (goog.a11y.aria.getRole(element)); + goog.asserts.assert(elementRole != null, + 'The element ARIA role cannot be null.'); + + goog.asserts.assert(goog.array.contains(allowedRoles, elementRole), + 'Non existing or incorrect role set for element.' + + 'The role set is "' + elementRole + + '". The role should be any of "' + allowedRoles + + '". Check the ARIA specification for more details ' + + 'http://www.w3.org/TR/wai-aria/roles.'); +}; + + +/** + * Gets the boolean value of an ARIA state/property. + * @param {!Element} element The element to get the ARIA state for. + * @param {!goog.a11y.aria.State|string} stateName the ARIA state name. + * @return {?boolean} Boolean value for the ARIA state value or null if + * the state value is not 'true', not 'false', or not set. + */ +goog.a11y.aria.getStateBoolean = function(element, stateName) { + var attr = + /** @type {string|boolean} */ (element.getAttribute( + goog.a11y.aria.getAriaAttributeName_(stateName))); + goog.asserts.assert( + goog.isBoolean(attr) || attr == null || attr == 'true' || + attr == 'false'); + if (attr == null) { + return attr; + } + return goog.isBoolean(attr) ? attr : attr == 'true'; +}; + + +/** + * Gets the number value of an ARIA state/property. + * @param {!Element} element The element to get the ARIA state for. + * @param {!goog.a11y.aria.State|string} stateName the ARIA state name. + * @return {?number} Number value for the ARIA state value or null if + * the state value is not a number or not set. + */ +goog.a11y.aria.getStateNumber = function(element, stateName) { + var attr = + /** @type {string|number} */ (element.getAttribute( + goog.a11y.aria.getAriaAttributeName_(stateName))); + goog.asserts.assert((attr == null || !isNaN(Number(attr))) && + !goog.isBoolean(attr)); + return attr == null ? null : Number(attr); +}; + + +/** + * Gets the string value of an ARIA state/property. + * @param {!Element} element The element to get the ARIA state for. + * @param {!goog.a11y.aria.State|string} stateName the ARIA state name. + * @return {?string} String value for the ARIA state value or null if + * the state value is empty string or not set. + */ +goog.a11y.aria.getStateString = function(element, stateName) { + var attr = element.getAttribute( + goog.a11y.aria.getAriaAttributeName_(stateName)); + goog.asserts.assert((attr == null || goog.isString(attr)) && + isNaN(Number(attr)) && attr != 'true' && attr != 'false'); + return attr == null ? null : attr; +}; + + +/** + * Gets array of strings value of the specified state or + * property for the element. + * Only to be used internally by the ARIA library in goog.a11y.aria.*. + * @param {!Element} element DOM node to get state from. + * @param {!goog.a11y.aria.State} stateName State name. + * @return {!goog.array.ArrayLike.<string>} string Array + * value of the state attribute. + */ +goog.a11y.aria.getStringArrayStateInternalUtil = function(element, stateName) { + var attrValue = element.getAttribute( + goog.a11y.aria.getAriaAttributeName_(stateName)); + return goog.a11y.aria.splitStringOnWhitespace_(attrValue); +}; + + +/** + * Splits the input stringValue on whitespace. + * @param {string} stringValue The value of the string to split. + * @return {!goog.array.ArrayLike.<string>} string Array + * value as result of the split. + * @private + */ +goog.a11y.aria.splitStringOnWhitespace_ = function(stringValue) { + return stringValue ? stringValue.split(/\s+/) : []; +}; + + +/** + * Adds the 'aria-' prefix to ariaName. + * @param {string} ariaName ARIA state/property name. + * @private + * @return {string} The ARIA attribute name with added 'aria-' prefix. + * @throws {Error} If no such attribute exists. + */ +goog.a11y.aria.getAriaAttributeName_ = function(ariaName) { + if (goog.asserts.ENABLE_ASSERTS) { + goog.asserts.assert(ariaName, 'ARIA attribute cannot be empty.'); + goog.asserts.assert(goog.object.containsValue( + goog.a11y.aria.State, ariaName), + 'No such ARIA attribute ' + ariaName); + } + return goog.a11y.aria.ARIA_PREFIX_ + ariaName; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/attributes.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/attributes.js new file mode 100644 index 0000000..f4e0a3d --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/attributes.js @@ -0,0 +1,389 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/** + * @fileoverview The file contains generated enumerations for ARIA states + * and properties as defined by W3C ARIA standard: + * http://www.w3.org/TR/wai-aria/. + * + * This is auto-generated code. Do not manually edit! For more details + * about how to edit it via the generator check go/closure-ariagen. + */ + +goog.provide('goog.a11y.aria.AutoCompleteValues'); +goog.provide('goog.a11y.aria.CheckedValues'); +goog.provide('goog.a11y.aria.DropEffectValues'); +goog.provide('goog.a11y.aria.ExpandedValues'); +goog.provide('goog.a11y.aria.GrabbedValues'); +goog.provide('goog.a11y.aria.InvalidValues'); +goog.provide('goog.a11y.aria.LivePriority'); +goog.provide('goog.a11y.aria.OrientationValues'); +goog.provide('goog.a11y.aria.PressedValues'); +goog.provide('goog.a11y.aria.RelevantValues'); +goog.provide('goog.a11y.aria.SelectedValues'); +goog.provide('goog.a11y.aria.SortValues'); +goog.provide('goog.a11y.aria.State'); + + +/** + * ARIA states and properties. + * @enum {string} + */ +goog.a11y.aria.State = { + // ARIA property for setting the currently active descendant of an element, + // for example the selected item in a list box. Value: ID of an element. + ACTIVEDESCENDANT: 'activedescendant', + + // ARIA property that, if true, indicates that all of a changed region should + // be presented, instead of only parts. Value: one of {true, false}. + ATOMIC: 'atomic', + + // ARIA property to specify that input completion is provided. Value: + // one of {'inline', 'list', 'both', 'none'}. + AUTOCOMPLETE: 'autocomplete', + + // ARIA state to indicate that an element and its subtree are being updated. + // Value: one of {true, false}. + BUSY: 'busy', + + // ARIA state for a checked item. Value: one of {'true', 'false', 'mixed', + // undefined}. + CHECKED: 'checked', + + // ARIA property that identifies the element or elements whose contents or + // presence are controlled by this element. + // Value: space-separated IDs of other elements. + CONTROLS: 'controls', + + // ARIA property that identifies the element or elements that describe + // this element. Value: space-separated IDs of other elements. + DESCRIBEDBY: 'describedby', + + // ARIA state for a disabled item. Value: one of {true, false}. + DISABLED: 'disabled', + + // ARIA property that indicates what functions can be performed when a + // dragged object is released on the drop target. Value: one of + // {'copy', 'move', 'link', 'execute', 'popup', 'none'}. + DROPEFFECT: 'dropeffect', + + // ARIA state for setting whether the element like a tree node is expanded. + // Value: one of {true, false, undefined}. + EXPANDED: 'expanded', + + // ARIA property that identifies the next element (or elements) in the + // recommended reading order of content. Value: space-separated ids of + // elements to flow to. + FLOWTO: 'flowto', + + // ARIA state that indicates an element's "grabbed" state in drag-and-drop. + // Value: one of {true, false, undefined}. + GRABBED: 'grabbed', + + // ARIA property indicating whether the element has a popup. + // Value: one of {true, false}. + HASPOPUP: 'haspopup', + + // ARIA state indicating that the element is not visible or perceivable + // to any user. Value: one of {true, false}. + HIDDEN: 'hidden', + + // ARIA state indicating that the entered value does not conform. Value: + // one of {false, true, 'grammar', 'spelling'} + INVALID: 'invalid', + + // ARIA property that provides a label to override any other text, value, or + // contents used to describe this element. Value: string. + LABEL: 'label', + + // ARIA property for setting the element which labels another element. + // Value: space-separated IDs of elements. + LABELLEDBY: 'labelledby', + + // ARIA property for setting the level of an element in the hierarchy. + // Value: integer. + LEVEL: 'level', + + // ARIA property indicating that an element will be updated, and + // describes the types of updates the user agents, assistive technologies, + // and user can expect from the live region. Value: one of {'off', 'polite', + // 'assertive'}. + LIVE: 'live', + + // ARIA property indicating whether a text box can accept multiline input. + // Value: one of {true, false}. + MULTILINE: 'multiline', + + // ARIA property indicating if the user may select more than one item. + // Value: one of {true, false}. + MULTISELECTABLE: 'multiselectable', + + // ARIA property indicating if the element is horizontal or vertical. + // Value: one of {'vertical', 'horizontal'}. + ORIENTATION: 'orientation', + + // ARIA property creating a visual, functional, or contextual parent/child + // relationship when the DOM hierarchy can't be used to represent it. + // Value: Space-separated IDs of elements. + OWNS: 'owns', + + // ARIA property that defines an element's number of position in a list. + // Value: integer. + POSINSET: 'posinset', + + // ARIA state for a pressed item. + // Value: one of {true, false, undefined, 'mixed'}. + PRESSED: 'pressed', + + // ARIA property indicating that an element is not editable. + // Value: one of {true, false}. + READONLY: 'readonly', + + // ARIA property indicating that change notifications within this subtree + // of a live region should be announced. Value: one of {'additions', + // 'removals', 'text', 'all', 'additions text'}. + RELEVANT: 'relevant', + + // ARIA property indicating that user input is required on this element + // before a form may be submitted. Value: one of {true, false}. + REQUIRED: 'required', + + // ARIA state for setting the currently selected item in the list. + // Value: one of {true, false, undefined}. + SELECTED: 'selected', + + // ARIA property defining the number of items in a list. Value: integer. + SETSIZE: 'setsize', + + // ARIA property indicating if items are sorted. Value: one of {'ascending', + // 'descending', 'none', 'other'}. + SORT: 'sort', + + // ARIA property for slider maximum value. Value: number. + VALUEMAX: 'valuemax', + + // ARIA property for slider minimum value. Value: number. + VALUEMIN: 'valuemin', + + // ARIA property for slider active value. Value: number. + VALUENOW: 'valuenow', + + // ARIA property for slider active value represented as text. + // Value: string. + VALUETEXT: 'valuetext' +}; + + +/** + * ARIA state values for AutoCompleteValues. + * @enum {string} + */ +goog.a11y.aria.AutoCompleteValues = { + // The system provides text after the caret as a suggestion + // for how to complete the field. + INLINE: 'inline', + // A list of choices appears from which the user can choose, + // but the edit box retains focus. + LIST: 'list', + // A list of choices appears and the currently selected suggestion + // also appears inline. + BOTH: 'both', + // No input completion suggestions are provided. + NONE: 'none' +}; + + +/** + * ARIA state values for DropEffectValues. + * @enum {string} + */ +goog.a11y.aria.DropEffectValues = { + // A duplicate of the source object will be dropped into the target. + COPY: 'copy', + // The source object will be removed from its current location + // and dropped into the target. + MOVE: 'move', + // A reference or shortcut to the dragged object + // will be created in the target object. + LINK: 'link', + // A function supported by the drop target is + // executed, using the drag source as an input. + EXECUTE: 'execute', + // There is a popup menu or dialog that allows the user to choose + // one of the drag operations (copy, move, link, execute) and any other + // drag functionality, such as cancel. + POPUP: 'popup', + // No operation can be performed; effectively + // cancels the drag operation if an attempt is made to drop on this object. + NONE: 'none' +}; + + +/** + * ARIA state values for LivePriority. + * @enum {string} + */ +goog.a11y.aria.LivePriority = { + // Updates to the region will not be presented to the user + // unless the assitive technology is currently focused on that region. + OFF: 'off', + // (Background change) Assistive technologies SHOULD announce + // updates at the next graceful opportunity, such as at the end of + // speaking the current sentence or when the user pauses typing. + POLITE: 'polite', + // This information has the highest priority and assistive + // technologies SHOULD notify the user immediately. + // Because an interruption may disorient users or cause them to not complete + // their current task, authors SHOULD NOT use the assertive value unless the + // interruption is imperative. + ASSERTIVE: 'assertive' +}; + + +/** + * ARIA state values for OrientationValues. + * @enum {string} + */ +goog.a11y.aria.OrientationValues = { + // The element is oriented vertically. + VERTICAL: 'vertical', + // The element is oriented horizontally. + HORIZONTAL: 'horizontal' +}; + + +/** + * ARIA state values for RelevantValues. + * @enum {string} + */ +goog.a11y.aria.RelevantValues = { + // Element nodes are added to the DOM within the live region. + ADDITIONS: 'additions', + // Text or element nodes within the live region are removed from the DOM. + REMOVALS: 'removals', + // Text is added to any DOM descendant nodes of the live region. + TEXT: 'text', + // Equivalent to the combination of all values, "additions removals text". + ALL: 'all' +}; + + +/** + * ARIA state values for SortValues. + * @enum {string} + */ +goog.a11y.aria.SortValues = { + // Items are sorted in ascending order by this column. + ASCENDING: 'ascending', + // Items are sorted in descending order by this column. + DESCENDING: 'descending', + // There is no defined sort applied to the column. + NONE: 'none', + // A sort algorithm other than ascending or descending has been applied. + OTHER: 'other' +}; + + +/** + * ARIA state values for CheckedValues. + * @enum {string} + */ +goog.a11y.aria.CheckedValues = { + // The selectable element is checked. + TRUE: 'true', + // The selectable element is not checked. + FALSE: 'false', + // Indicates a mixed mode value for a tri-state + // checkbox or menuitemcheckbox. + MIXED: 'mixed', + // The element does not support being checked. + UNDEFINED: 'undefined' +}; + + +/** + * ARIA state values for ExpandedValues. + * @enum {string} + */ +goog.a11y.aria.ExpandedValues = { + // The element, or another grouping element it controls, is expanded. + TRUE: 'true', + // The element, or another grouping element it controls, is collapsed. + FALSE: 'false', + // The element, or another grouping element + // it controls, is neither expandable nor collapsible; all its + // child elements are shown or there are no child elements. + UNDEFINED: 'undefined' +}; + + +/** + * ARIA state values for GrabbedValues. + * @enum {string} + */ +goog.a11y.aria.GrabbedValues = { + // Indicates that the element has been "grabbed" for dragging. + TRUE: 'true', + // Indicates that the element supports being dragged. + FALSE: 'false', + // Indicates that the element does not support being dragged. + UNDEFINED: 'undefined' +}; + + +/** + * ARIA state values for InvalidValues. + * @enum {string} + */ +goog.a11y.aria.InvalidValues = { + // There are no detected errors in the value. + FALSE: 'false', + // The value entered by the user has failed validation. + TRUE: 'true', + // A grammatical error was detected. + GRAMMAR: 'grammar', + // A spelling error was detected. + SPELLING: 'spelling' +}; + + +/** + * ARIA state values for PressedValues. + * @enum {string} + */ +goog.a11y.aria.PressedValues = { + // The element is pressed. + TRUE: 'true', + // The element supports being pressed but is not currently pressed. + FALSE: 'false', + // Indicates a mixed mode value for a tri-state toggle button. + MIXED: 'mixed', + // The element does not support being pressed. + UNDEFINED: 'undefined' +}; + + +/** + * ARIA state values for SelectedValues. + * @enum {string} + */ +goog.a11y.aria.SelectedValues = { + // The selectable element is selected. + TRUE: 'true', + // The selectable element is not selected. + FALSE: 'false', + // The element is not selectable. + UNDEFINED: 'undefined' +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/datatables.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/datatables.js new file mode 100644 index 0000000..05e2fb9 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/datatables.js @@ -0,0 +1,68 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + + +/** + * @fileoverview The file contains data tables generated from the ARIA + * standard schema http://www.w3.org/TR/wai-aria/. + * + * This is auto-generated code. Do not manually edit! + */ + +goog.provide('goog.a11y.aria.datatables'); + +goog.require('goog.a11y.aria.State'); +goog.require('goog.object'); + + +/** + * A map that contains mapping between an ARIA state and the default value + * for it. Note that not all ARIA states have default values. + * + * @type {Object.<!(goog.a11y.aria.State|string), (string|boolean|number)>} + */ +goog.a11y.aria.DefaultStateValueMap_; + + +/** + * A method that creates a map that contains mapping between an ARIA state and + * the default value for it. Note that not all ARIA states have default values. + * + * @return {!Object.<!(goog.a11y.aria.State|string), (string|boolean|number)>} + * The names for each of the notification methods. + */ +goog.a11y.aria.datatables.getDefaultValuesMap = function() { + if (!goog.a11y.aria.DefaultStateValueMap_) { + goog.a11y.aria.DefaultStateValueMap_ = goog.object.create( + goog.a11y.aria.State.ATOMIC, false, + goog.a11y.aria.State.AUTOCOMPLETE, 'none', + goog.a11y.aria.State.DROPEFFECT, 'none', + goog.a11y.aria.State.HASPOPUP, false, + goog.a11y.aria.State.LIVE, 'off', + goog.a11y.aria.State.MULTILINE, false, + goog.a11y.aria.State.MULTISELECTABLE, false, + goog.a11y.aria.State.ORIENTATION, 'vertical', + goog.a11y.aria.State.READONLY, false, + goog.a11y.aria.State.RELEVANT, 'additions text', + goog.a11y.aria.State.REQUIRED, false, + goog.a11y.aria.State.SORT, 'none', + goog.a11y.aria.State.BUSY, false, + goog.a11y.aria.State.DISABLED, false, + goog.a11y.aria.State.HIDDEN, false, + goog.a11y.aria.State.INVALID, 'false'); + } + + return goog.a11y.aria.DefaultStateValueMap_; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/roles.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/roles.js new file mode 100644 index 0000000..a282cc2 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/a11y/aria/roles.js @@ -0,0 +1,216 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/** + * @fileoverview The file contains generated enumerations for ARIA roles + * as defined by W3C ARIA standard: http://www.w3.org/TR/wai-aria/. + * + * This is auto-generated code. Do not manually edit! For more details + * about how to edit it via the generator check go/closure-ariagen. + */ + +goog.provide('goog.a11y.aria.Role'); + + +/** + * ARIA role values. + * @enum {string} + */ +goog.a11y.aria.Role = { + // ARIA role for an alert element that doesn't need to be explicitly closed. + ALERT: 'alert', + + // ARIA role for an alert dialog element that takes focus and must be closed. + ALERTDIALOG: 'alertdialog', + + // ARIA role for an application that implements its own keyboard navigation. + APPLICATION: 'application', + + // ARIA role for an article. + ARTICLE: 'article', + + // ARIA role for a banner containing mostly site content, not page content. + BANNER: 'banner', + + // ARIA role for a button element. + BUTTON: 'button', + + // ARIA role for a checkbox button element; use with the CHECKED state. + CHECKBOX: 'checkbox', + + // ARIA role for a column header of a table or grid. + COLUMNHEADER: 'columnheader', + + // ARIA role for a combo box element. + COMBOBOX: 'combobox', + + // ARIA role for a supporting section of the document. + COMPLEMENTARY: 'complementary', + + // ARIA role for a large perceivable region that contains information + // about the parent document. + CONTENTINFO: 'contentinfo', + + // ARIA role for a definition of a term or concept. + DEFINITION: 'definition', + + // ARIA role for a dialog, some descendant must take initial focus. + DIALOG: 'dialog', + + // ARIA role for a directory, like a table of contents. + DIRECTORY: 'directory', + + // ARIA role for a part of a page that's a document, not a web application. + DOCUMENT: 'document', + + // ARIA role for a landmark region logically considered one form. + FORM: 'form', + + // ARIA role for an interactive control of tabular data. + GRID: 'grid', + + // ARIA role for a cell in a grid. + GRIDCELL: 'gridcell', + + // ARIA role for a group of related elements like tree item siblings. + GROUP: 'group', + + // ARIA role for a heading element. + HEADING: 'heading', + + // ARIA role for a container of elements that together comprise one image. + IMG: 'img', + + // ARIA role for a link. + LINK: 'link', + + // ARIA role for a list of non-interactive list items. + LIST: 'list', + + // ARIA role for a listbox. + LISTBOX: 'listbox', + + // ARIA role for a list item. + LISTITEM: 'listitem', + + // ARIA role for a live region where new information is added. + LOG: 'log', + + // ARIA landmark role for the main content in a document. Use only once. + MAIN: 'main', + + // ARIA role for a live region of non-essential information that changes. + MARQUEE: 'marquee', + + // ARIA role for a mathematical expression. + MATH: 'math', + + // ARIA role for a popup menu. + MENU: 'menu', + + // ARIA role for a menubar element containing menu elements. + MENUBAR: 'menubar', + + // ARIA role for menu item elements. + MENU_ITEM: 'menuitem', + + // ARIA role for a checkbox box element inside a menu. + MENU_ITEM_CHECKBOX: 'menuitemcheckbox', + + // ARIA role for a radio button element inside a menu. + MENU_ITEM_RADIO: 'menuitemradio', + + // ARIA landmark role for a collection of navigation links. + NAVIGATION: 'navigation', + + // ARIA role for a section ancillary to the main content. + NOTE: 'note', + + // ARIA role for option items that are children of combobox, listbox, menu, + // radiogroup, or tree elements. + OPTION: 'option', + + // ARIA role for ignorable cosmetic elements with no semantic significance. + PRESENTATION: 'presentation', + + // ARIA role for a progress bar element. + PROGRESSBAR: 'progressbar', + + // ARIA role for a radio button element. + RADIO: 'radio', + + // ARIA role for a group of connected radio button elements. + RADIOGROUP: 'radiogroup', + + // ARIA role for an important region of the page. + REGION: 'region', + + // ARIA role for a row of cells in a grid. + ROW: 'row', + + // ARIA role for a group of one or more rows in a grid. + ROWGROUP: 'rowgroup', + + // ARIA role for a row header of a table or grid. + ROWHEADER: 'rowheader', + + // ARIA role for a scrollbar element. + SCROLLBAR: 'scrollbar', + + // ARIA landmark role for a part of the page providing search functionality. + SEARCH: 'search', + + // ARIA role for a menu separator. + SEPARATOR: 'separator', + + // ARIA role for a slider. + SLIDER: 'slider', + + // ARIA role for a spin button. + SPINBUTTON: 'spinbutton', + + // ARIA role for a live region with advisory info less severe than an alert. + STATUS: 'status', + + // ARIA role for a tab button. + TAB: 'tab', + + // ARIA role for a tab bar (i.e. a list of tab buttons). + TAB_LIST: 'tablist', + + // ARIA role for a tab page (i.e. the element holding tab contents). + TAB_PANEL: 'tabpanel', + + // ARIA role for a textbox element. + TEXTBOX: 'textbox', + + // ARIA role for an element displaying elapsed time or time remaining. + TIMER: 'timer', + + // ARIA role for a toolbar element. + TOOLBAR: 'toolbar', + + // ARIA role for a tooltip element. + TOOLTIP: 'tooltip', + + // ARIA role for a tree. + TREE: 'tree', + + // ARIA role for a grid whose rows can be expanded and collapsed like a tree. + TREEGRID: 'treegrid', + + // ARIA role for a tree item that sometimes may be expanded or collapsed. + TREEITEM: 'treeitem' +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/array/array.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/array/array.js new file mode 100644 index 0000000..2d8c23f --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/array/array.js @@ -0,0 +1,1570 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utilities for manipulating arrays. + * + */ + + +goog.provide('goog.array'); +goog.provide('goog.array.ArrayLike'); + +goog.require('goog.asserts'); + + +/** + * @define {boolean} NATIVE_ARRAY_PROTOTYPES indicates whether the code should + * rely on Array.prototype functions, if available. + * + * The Array.prototype functions can be defined by external libraries like + * Prototype and setting this flag to false forces closure to use its own + * goog.array implementation. + * + * If your javascript can be loaded by a third party site and you are wary about + * relying on the prototype functions, specify + * "--define goog.NATIVE_ARRAY_PROTOTYPES=false" to the JSCompiler. + * + * Setting goog.TRUSTED_SITE to false will automatically set + * NATIVE_ARRAY_PROTOTYPES to false. + */ +goog.define('goog.NATIVE_ARRAY_PROTOTYPES', goog.TRUSTED_SITE); + + +/** + * @define {boolean} If true, JSCompiler will use the native implementation of + * array functions where appropriate (e.g., {@code Array#filter}) and remove the + * unused pure JS implementation. + */ +goog.define('goog.array.ASSUME_NATIVE_FUNCTIONS', false); + + +/** + * @typedef {Array|NodeList|Arguments|{length: number}} + */ +goog.array.ArrayLike; + + +/** + * Returns the last element in an array without removing it. + * Same as goog.array.last. + * @param {Array.<T>|goog.array.ArrayLike} array The array. + * @return {T} Last item in array. + * @template T + */ +goog.array.peek = function(array) { + return array[array.length - 1]; +}; + + +/** + * Returns the last element in an array without removing it. + * Same as goog.array.peek. + * @param {Array.<T>|goog.array.ArrayLike} array The array. + * @return {T} Last item in array. + * @template T + */ +goog.array.last = goog.array.peek; + + +/** + * Reference to the original {@code Array.prototype}. + * @private + */ +goog.array.ARRAY_PROTOTYPE_ = Array.prototype; + + +// NOTE(arv): Since most of the array functions are generic it allows you to +// pass an array-like object. Strings have a length and are considered array- +// like. However, the 'in' operator does not work on strings so we cannot just +// use the array path even if the browser supports indexing into strings. We +// therefore end up splitting the string. + + +/** + * Returns the index of the first element of an array with a specified value, or + * -1 if the element is not present in the array. + * + * See {@link http://tinyurl.com/developer-mozilla-org-array-indexof} + * + * @param {Array.<T>|goog.array.ArrayLike} arr The array to be searched. + * @param {T} obj The object for which we are searching. + * @param {number=} opt_fromIndex The index at which to start the search. If + * omitted the search starts at index 0. + * @return {number} The index of the first matching array element. + * @template T + */ +goog.array.indexOf = goog.NATIVE_ARRAY_PROTOTYPES && + (goog.array.ASSUME_NATIVE_FUNCTIONS || + goog.array.ARRAY_PROTOTYPE_.indexOf) ? + function(arr, obj, opt_fromIndex) { + goog.asserts.assert(arr.length != null); + + return goog.array.ARRAY_PROTOTYPE_.indexOf.call(arr, obj, opt_fromIndex); + } : + function(arr, obj, opt_fromIndex) { + var fromIndex = opt_fromIndex == null ? + 0 : (opt_fromIndex < 0 ? + Math.max(0, arr.length + opt_fromIndex) : opt_fromIndex); + + if (goog.isString(arr)) { + // Array.prototype.indexOf uses === so only strings should be found. + if (!goog.isString(obj) || obj.length != 1) { + return -1; + } + return arr.indexOf(obj, fromIndex); + } + + for (var i = fromIndex; i < arr.length; i++) { + if (i in arr && arr[i] === obj) + return i; + } + return -1; + }; + + +/** + * Returns the index of the last element of an array with a specified value, or + * -1 if the element is not present in the array. + * + * See {@link http://tinyurl.com/developer-mozilla-org-array-lastindexof} + * + * @param {!Array.<T>|!goog.array.ArrayLike} arr The array to be searched. + * @param {T} obj The object for which we are searching. + * @param {?number=} opt_fromIndex The index at which to start the search. If + * omitted the search starts at the end of the array. + * @return {number} The index of the last matching array element. + * @template T + */ +goog.array.lastIndexOf = goog.NATIVE_ARRAY_PROTOTYPES && + (goog.array.ASSUME_NATIVE_FUNCTIONS || + goog.array.ARRAY_PROTOTYPE_.lastIndexOf) ? + function(arr, obj, opt_fromIndex) { + goog.asserts.assert(arr.length != null); + + // Firefox treats undefined and null as 0 in the fromIndex argument which + // leads it to always return -1 + var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex; + return goog.array.ARRAY_PROTOTYPE_.lastIndexOf.call(arr, obj, fromIndex); + } : + function(arr, obj, opt_fromIndex) { + var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex; + + if (fromIndex < 0) { + fromIndex = Math.max(0, arr.length + fromIndex); + } + + if (goog.isString(arr)) { + // Array.prototype.lastIndexOf uses === so only strings should be found. + if (!goog.isString(obj) || obj.length != 1) { + return -1; + } + return arr.lastIndexOf(obj, fromIndex); + } + + for (var i = fromIndex; i >= 0; i--) { + if (i in arr && arr[i] === obj) + return i; + } + return -1; + }; + + +/** + * Calls a function for each element in an array. Skips holes in the array. + * See {@link http://tinyurl.com/developer-mozilla-org-array-foreach} + * + * @param {Array.<T>|goog.array.ArrayLike} arr Array or array like object over + * which to iterate. + * @param {?function(this: S, T, number, ?): ?} f The function to call for every + * element. This function takes 3 arguments (the element, the index and the + * array). The return value is ignored. + * @param {S=} opt_obj The object to be used as the value of 'this' within f. + * @template T,S + */ +goog.array.forEach = goog.NATIVE_ARRAY_PROTOTYPES && + (goog.array.ASSUME_NATIVE_FUNCTIONS || + goog.array.ARRAY_PROTOTYPE_.forEach) ? + function(arr, f, opt_obj) { + goog.asserts.assert(arr.length != null); + + goog.array.ARRAY_PROTOTYPE_.forEach.call(arr, f, opt_obj); + } : + function(arr, f, opt_obj) { + var l = arr.length; // must be fixed during loop... see docs + var arr2 = goog.isString(arr) ? arr.split('') : arr; + for (var i = 0; i < l; i++) { + if (i in arr2) { + f.call(opt_obj, arr2[i], i, arr); + } + } + }; + + +/** + * Calls a function for each element in an array, starting from the last + * element rather than the first. + * + * @param {Array.<T>|goog.array.ArrayLike} arr Array or array + * like object over which to iterate. + * @param {?function(this: S, T, number, ?): ?} f The function to call for every + * element. This function + * takes 3 arguments (the element, the index and the array). The return + * value is ignored. + * @param {S=} opt_obj The object to be used as the value of 'this' + * within f. + * @template T,S + */ +goog.array.forEachRight = function(arr, f, opt_obj) { + var l = arr.length; // must be fixed during loop... see docs + var arr2 = goog.isString(arr) ? arr.split('') : arr; + for (var i = l - 1; i >= 0; --i) { + if (i in arr2) { + f.call(opt_obj, arr2[i], i, arr); + } + } +}; + + +/** + * Calls a function for each element in an array, and if the function returns + * true adds the element to a new array. + * + * See {@link http://tinyurl.com/developer-mozilla-org-array-filter} + * + * @param {Array.<T>|goog.array.ArrayLike} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, T, number, ?):boolean} f The function to call for + * every element. This function + * takes 3 arguments (the element, the index and the array) and must + * return a Boolean. If the return value is true the element is added to the + * result array. If it is false the element is not included. + * @param {S=} opt_obj The object to be used as the value of 'this' + * within f. + * @return {!Array.<T>} a new array in which only elements that passed the test + * are present. + * @template T,S + */ +goog.array.filter = goog.NATIVE_ARRAY_PROTOTYPES && + (goog.array.ASSUME_NATIVE_FUNCTIONS || + goog.array.ARRAY_PROTOTYPE_.filter) ? + function(arr, f, opt_obj) { + goog.asserts.assert(arr.length != null); + + return goog.array.ARRAY_PROTOTYPE_.filter.call(arr, f, opt_obj); + } : + function(arr, f, opt_obj) { + var l = arr.length; // must be fixed during loop... see docs + var res = []; + var resLength = 0; + var arr2 = goog.isString(arr) ? arr.split('') : arr; + for (var i = 0; i < l; i++) { + if (i in arr2) { + var val = arr2[i]; // in case f mutates arr2 + if (f.call(opt_obj, val, i, arr)) { + res[resLength++] = val; + } + } + } + return res; + }; + + +/** + * Calls a function for each element in an array and inserts the result into a + * new array. + * + * See {@link http://tinyurl.com/developer-mozilla-org-array-map} + * + * @param {Array.<VALUE>|goog.array.ArrayLike} arr Array or array like object + * over which to iterate. + * @param {function(this:THIS, VALUE, number, ?): RESULT} f The function to call + * for every element. This function takes 3 arguments (the element, + * the index and the array) and should return something. The result will be + * inserted into a new array. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within f. + * @return {!Array.<RESULT>} a new array with the results from f. + * @template THIS, VALUE, RESULT + */ +goog.array.map = goog.NATIVE_ARRAY_PROTOTYPES && + (goog.array.ASSUME_NATIVE_FUNCTIONS || + goog.array.ARRAY_PROTOTYPE_.map) ? + function(arr, f, opt_obj) { + goog.asserts.assert(arr.length != null); + + return goog.array.ARRAY_PROTOTYPE_.map.call(arr, f, opt_obj); + } : + function(arr, f, opt_obj) { + var l = arr.length; // must be fixed during loop... see docs + var res = new Array(l); + var arr2 = goog.isString(arr) ? arr.split('') : arr; + for (var i = 0; i < l; i++) { + if (i in arr2) { + res[i] = f.call(opt_obj, arr2[i], i, arr); + } + } + return res; + }; + + +/** + * Passes every element of an array into a function and accumulates the result. + * + * See {@link http://tinyurl.com/developer-mozilla-org-array-reduce} + * + * For example: + * var a = [1, 2, 3, 4]; + * goog.array.reduce(a, function(r, v, i, arr) {return r + v;}, 0); + * returns 10 + * + * @param {Array.<T>|goog.array.ArrayLike} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, R, T, number, ?) : R} f The function to call for + * every element. This function + * takes 4 arguments (the function's previous result or the initial value, + * the value of the current array element, the current array index, and the + * array itself) + * function(previousValue, currentValue, index, array). + * @param {?} val The initial value to pass into the function on the first call. + * @param {S=} opt_obj The object to be used as the value of 'this' + * within f. + * @return {R} Result of evaluating f repeatedly across the values of the array. + * @template T,S,R + */ +goog.array.reduce = goog.NATIVE_ARRAY_PROTOTYPES && + (goog.array.ASSUME_NATIVE_FUNCTIONS || + goog.array.ARRAY_PROTOTYPE_.reduce) ? + function(arr, f, val, opt_obj) { + goog.asserts.assert(arr.length != null); + if (opt_obj) { + f = goog.bind(f, opt_obj); + } + return goog.array.ARRAY_PROTOTYPE_.reduce.call(arr, f, val); + } : + function(arr, f, val, opt_obj) { + var rval = val; + goog.array.forEach(arr, function(val, index) { + rval = f.call(opt_obj, rval, val, index, arr); + }); + return rval; + }; + + +/** + * Passes every element of an array into a function and accumulates the result, + * starting from the last element and working towards the first. + * + * See {@link http://tinyurl.com/developer-mozilla-org-array-reduceright} + * + * For example: + * var a = ['a', 'b', 'c']; + * goog.array.reduceRight(a, function(r, v, i, arr) {return r + v;}, ''); + * returns 'cba' + * + * @param {Array.<T>|goog.array.ArrayLike} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, R, T, number, ?) : R} f The function to call for + * every element. This function + * takes 4 arguments (the function's previous result or the initial value, + * the value of the current array element, the current array index, and the + * array itself) + * function(previousValue, currentValue, index, array). + * @param {?} val The initial value to pass into the function on the first call. + * @param {S=} opt_obj The object to be used as the value of 'this' + * within f. + * @return {R} Object returned as a result of evaluating f repeatedly across the + * values of the array. + * @template T,S,R + */ +goog.array.reduceRight = goog.NATIVE_ARRAY_PROTOTYPES && + (goog.array.ASSUME_NATIVE_FUNCTIONS || + goog.array.ARRAY_PROTOTYPE_.reduceRight) ? + function(arr, f, val, opt_obj) { + goog.asserts.assert(arr.length != null); + if (opt_obj) { + f = goog.bind(f, opt_obj); + } + return goog.array.ARRAY_PROTOTYPE_.reduceRight.call(arr, f, val); + } : + function(arr, f, val, opt_obj) { + var rval = val; + goog.array.forEachRight(arr, function(val, index) { + rval = f.call(opt_obj, rval, val, index, arr); + }); + return rval; + }; + + +/** + * Calls f for each element of an array. If any call returns true, some() + * returns true (without checking the remaining elements). If all calls + * return false, some() returns false. + * + * See {@link http://tinyurl.com/developer-mozilla-org-array-some} + * + * @param {Array.<T>|goog.array.ArrayLike} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, T, number, ?) : boolean} f The function to call for + * for every element. This function takes 3 arguments (the element, the + * index and the array) and should return a boolean. + * @param {S=} opt_obj The object to be used as the value of 'this' + * within f. + * @return {boolean} true if any element passes the test. + * @template T,S + */ +goog.array.some = goog.NATIVE_ARRAY_PROTOTYPES && + (goog.array.ASSUME_NATIVE_FUNCTIONS || + goog.array.ARRAY_PROTOTYPE_.some) ? + function(arr, f, opt_obj) { + goog.asserts.assert(arr.length != null); + + return goog.array.ARRAY_PROTOTYPE_.some.call(arr, f, opt_obj); + } : + function(arr, f, opt_obj) { + var l = arr.length; // must be fixed during loop... see docs + var arr2 = goog.isString(arr) ? arr.split('') : arr; + for (var i = 0; i < l; i++) { + if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) { + return true; + } + } + return false; + }; + + +/** + * Call f for each element of an array. If all calls return true, every() + * returns true. If any call returns false, every() returns false and + * does not continue to check the remaining elements. + * + * See {@link http://tinyurl.com/developer-mozilla-org-array-every} + * + * @param {Array.<T>|goog.array.ArrayLike} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, T, number, ?) : boolean} f The function to call for + * for every element. This function takes 3 arguments (the element, the + * index and the array) and should return a boolean. + * @param {S=} opt_obj The object to be used as the value of 'this' + * within f. + * @return {boolean} false if any element fails the test. + * @template T,S + */ +goog.array.every = goog.NATIVE_ARRAY_PROTOTYPES && + (goog.array.ASSUME_NATIVE_FUNCTIONS || + goog.array.ARRAY_PROTOTYPE_.every) ? + function(arr, f, opt_obj) { + goog.asserts.assert(arr.length != null); + + return goog.array.ARRAY_PROTOTYPE_.every.call(arr, f, opt_obj); + } : + function(arr, f, opt_obj) { + var l = arr.length; // must be fixed during loop... see docs + var arr2 = goog.isString(arr) ? arr.split('') : arr; + for (var i = 0; i < l; i++) { + if (i in arr2 && !f.call(opt_obj, arr2[i], i, arr)) { + return false; + } + } + return true; + }; + + +/** + * Counts the array elements that fulfill the predicate, i.e. for which the + * callback function returns true. Skips holes in the array. + * + * @param {!(Array.<T>|goog.array.ArrayLike)} arr Array or array like object + * over which to iterate. + * @param {function(this: S, T, number, ?): boolean} f The function to call for + * every element. Takes 3 arguments (the element, the index and the array). + * @param {S=} opt_obj The object to be used as the value of 'this' within f. + * @return {number} The number of the matching elements. + * @template T,S + */ +goog.array.count = function(arr, f, opt_obj) { + var count = 0; + goog.array.forEach(arr, function(element, index, arr) { + if (f.call(opt_obj, element, index, arr)) { + ++count; + } + }, opt_obj); + return count; +}; + + +/** + * Search an array for the first element that satisfies a given condition and + * return that element. + * @param {Array.<T>|goog.array.ArrayLike} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, T, number, ?) : boolean} f The function to call + * for every element. This function takes 3 arguments (the element, the + * index and the array) and should return a boolean. + * @param {S=} opt_obj An optional "this" context for the function. + * @return {?T} The first array element that passes the test, or null if no + * element is found. + * @template T,S + */ +goog.array.find = function(arr, f, opt_obj) { + var i = goog.array.findIndex(arr, f, opt_obj); + return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i]; +}; + + +/** + * Search an array for the first element that satisfies a given condition and + * return its index. + * @param {Array.<T>|goog.array.ArrayLike} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, T, number, ?) : boolean} f The function to call for + * every element. This function + * takes 3 arguments (the element, the index and the array) and should + * return a boolean. + * @param {S=} opt_obj An optional "this" context for the function. + * @return {number} The index of the first array element that passes the test, + * or -1 if no element is found. + * @template T,S + */ +goog.array.findIndex = function(arr, f, opt_obj) { + var l = arr.length; // must be fixed during loop... see docs + var arr2 = goog.isString(arr) ? arr.split('') : arr; + for (var i = 0; i < l; i++) { + if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) { + return i; + } + } + return -1; +}; + + +/** + * Search an array (in reverse order) for the last element that satisfies a + * given condition and return that element. + * @param {Array.<T>|goog.array.ArrayLike} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, T, number, ?) : boolean} f The function to call + * for every element. This function + * takes 3 arguments (the element, the index and the array) and should + * return a boolean. + * @param {S=} opt_obj An optional "this" context for the function. + * @return {?T} The last array element that passes the test, or null if no + * element is found. + * @template T,S + */ +goog.array.findRight = function(arr, f, opt_obj) { + var i = goog.array.findIndexRight(arr, f, opt_obj); + return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i]; +}; + + +/** + * Search an array (in reverse order) for the last element that satisfies a + * given condition and return its index. + * @param {Array.<T>|goog.array.ArrayLike} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, T, number, ?) : boolean} f The function to call + * for every element. This function + * takes 3 arguments (the element, the index and the array) and should + * return a boolean. + * @param {Object=} opt_obj An optional "this" context for the function. + * @return {number} The index of the last array element that passes the test, + * or -1 if no element is found. + * @template T,S + */ +goog.array.findIndexRight = function(arr, f, opt_obj) { + var l = arr.length; // must be fixed during loop... see docs + var arr2 = goog.isString(arr) ? arr.split('') : arr; + for (var i = l - 1; i >= 0; i--) { + if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) { + return i; + } + } + return -1; +}; + + +/** + * Whether the array contains the given object. + * @param {goog.array.ArrayLike} arr The array to test for the presence of the + * element. + * @param {*} obj The object for which to test. + * @return {boolean} true if obj is present. + */ +goog.array.contains = function(arr, obj) { + return goog.array.indexOf(arr, obj) >= 0; +}; + + +/** + * Whether the array is empty. + * @param {goog.array.ArrayLike} arr The array to test. + * @return {boolean} true if empty. + */ +goog.array.isEmpty = function(arr) { + return arr.length == 0; +}; + + +/** + * Clears the array. + * @param {goog.array.ArrayLike} arr Array or array like object to clear. + */ +goog.array.clear = function(arr) { + // For non real arrays we don't have the magic length so we delete the + // indices. + if (!goog.isArray(arr)) { + for (var i = arr.length - 1; i >= 0; i--) { + delete arr[i]; + } + } + arr.length = 0; +}; + + +/** + * Pushes an item into an array, if it's not already in the array. + * @param {Array.<T>} arr Array into which to insert the item. + * @param {T} obj Value to add. + * @template T + */ +goog.array.insert = function(arr, obj) { + if (!goog.array.contains(arr, obj)) { + arr.push(obj); + } +}; + + +/** + * Inserts an object at the given index of the array. + * @param {goog.array.ArrayLike} arr The array to modify. + * @param {*} obj The object to insert. + * @param {number=} opt_i The index at which to insert the object. If omitted, + * treated as 0. A negative index is counted from the end of the array. + */ +goog.array.insertAt = function(arr, obj, opt_i) { + goog.array.splice(arr, opt_i, 0, obj); +}; + + +/** + * Inserts at the given index of the array, all elements of another array. + * @param {goog.array.ArrayLike} arr The array to modify. + * @param {goog.array.ArrayLike} elementsToAdd The array of elements to add. + * @param {number=} opt_i The index at which to insert the object. If omitted, + * treated as 0. A negative index is counted from the end of the array. + */ +goog.array.insertArrayAt = function(arr, elementsToAdd, opt_i) { + goog.partial(goog.array.splice, arr, opt_i, 0).apply(null, elementsToAdd); +}; + + +/** + * Inserts an object into an array before a specified object. + * @param {Array.<T>} arr The array to modify. + * @param {T} obj The object to insert. + * @param {T=} opt_obj2 The object before which obj should be inserted. If obj2 + * is omitted or not found, obj is inserted at the end of the array. + * @template T + */ +goog.array.insertBefore = function(arr, obj, opt_obj2) { + var i; + if (arguments.length == 2 || (i = goog.array.indexOf(arr, opt_obj2)) < 0) { + arr.push(obj); + } else { + goog.array.insertAt(arr, obj, i); + } +}; + + +/** + * Removes the first occurrence of a particular value from an array. + * @param {Array.<T>|goog.array.ArrayLike} arr Array from which to remove + * value. + * @param {T} obj Object to remove. + * @return {boolean} True if an element was removed. + * @template T + */ +goog.array.remove = function(arr, obj) { + var i = goog.array.indexOf(arr, obj); + var rv; + if ((rv = i >= 0)) { + goog.array.removeAt(arr, i); + } + return rv; +}; + + +/** + * Removes from an array the element at index i + * @param {goog.array.ArrayLike} arr Array or array like object from which to + * remove value. + * @param {number} i The index to remove. + * @return {boolean} True if an element was removed. + */ +goog.array.removeAt = function(arr, i) { + goog.asserts.assert(arr.length != null); + + // use generic form of splice + // splice returns the removed items and if successful the length of that + // will be 1 + return goog.array.ARRAY_PROTOTYPE_.splice.call(arr, i, 1).length == 1; +}; + + +/** + * Removes the first value that satisfies the given condition. + * @param {Array.<T>|goog.array.ArrayLike} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, T, number, ?) : boolean} f The function to call + * for every element. This function + * takes 3 arguments (the element, the index and the array) and should + * return a boolean. + * @param {S=} opt_obj An optional "this" context for the function. + * @return {boolean} True if an element was removed. + * @template T,S + */ +goog.array.removeIf = function(arr, f, opt_obj) { + var i = goog.array.findIndex(arr, f, opt_obj); + if (i >= 0) { + goog.array.removeAt(arr, i); + return true; + } + return false; +}; + + +/** + * Returns a new array that is the result of joining the arguments. If arrays + * are passed then their items are added, however, if non-arrays are passed they + * will be added to the return array as is. + * + * Note that ArrayLike objects will be added as is, rather than having their + * items added. + * + * goog.array.concat([1, 2], [3, 4]) -> [1, 2, 3, 4] + * goog.array.concat(0, [1, 2]) -> [0, 1, 2] + * goog.array.concat([1, 2], null) -> [1, 2, null] + * + * There is bug in all current versions of IE (6, 7 and 8) where arrays created + * in an iframe become corrupted soon (not immediately) after the iframe is + * destroyed. This is common if loading data via goog.net.IframeIo, for example. + * This corruption only affects the concat method which will start throwing + * Catastrophic Errors (#-2147418113). + * + * See http://endoflow.com/scratch/corrupted-arrays.html for a test case. + * + * Internally goog.array should use this, so that all methods will continue to + * work on these broken array objects. + * + * @param {...*} var_args Items to concatenate. Arrays will have each item + * added, while primitives and objects will be added as is. + * @return {!Array} The new resultant array. + */ +goog.array.concat = function(var_args) { + return goog.array.ARRAY_PROTOTYPE_.concat.apply( + goog.array.ARRAY_PROTOTYPE_, arguments); +}; + + +/** + * Returns a new array that contains the contents of all the arrays passed. + * @param {...!Array.<T>} var_args + * @return {!Array.<T>} + * @template T + */ +goog.array.join = function(var_args) { + return goog.array.ARRAY_PROTOTYPE_.concat.apply( + goog.array.ARRAY_PROTOTYPE_, arguments); +}; + + +/** + * Converts an object to an array. + * @param {Array.<T>|goog.array.ArrayLike} object The object to convert to an + * array. + * @return {!Array.<T>} The object converted into an array. If object has a + * length property, every property indexed with a non-negative number + * less than length will be included in the result. If object does not + * have a length property, an empty array will be returned. + * @template T + */ +goog.array.toArray = function(object) { + var length = object.length; + + // If length is not a number the following it false. This case is kept for + // backwards compatibility since there are callers that pass objects that are + // not array like. + if (length > 0) { + var rv = new Array(length); + for (var i = 0; i < length; i++) { + rv[i] = object[i]; + } + return rv; + } + return []; +}; + + +/** + * Does a shallow copy of an array. + * @param {Array.<T>|goog.array.ArrayLike} arr Array or array-like object to + * clone. + * @return {!Array.<T>} Clone of the input array. + * @template T + */ +goog.array.clone = goog.array.toArray; + + +/** + * Extends an array with another array, element, or "array like" object. + * This function operates 'in-place', it does not create a new Array. + * + * Example: + * var a = []; + * goog.array.extend(a, [0, 1]); + * a; // [0, 1] + * goog.array.extend(a, 2); + * a; // [0, 1, 2] + * + * @param {Array.<VALUE>} arr1 The array to modify. + * @param {...(Array.<VALUE>|VALUE)} var_args The elements or arrays of elements + * to add to arr1. + * @template VALUE + */ +goog.array.extend = function(arr1, var_args) { + for (var i = 1; i < arguments.length; i++) { + var arr2 = arguments[i]; + // If we have an Array or an Arguments object we can just call push + // directly. + var isArrayLike; + if (goog.isArray(arr2) || + // Detect Arguments. ES5 says that the [[Class]] of an Arguments object + // is "Arguments" but only V8 and JSC/Safari gets this right. We instead + // detect Arguments by checking for array like and presence of "callee". + (isArrayLike = goog.isArrayLike(arr2)) && + // The getter for callee throws an exception in strict mode + // according to section 10.6 in ES5 so check for presence instead. + Object.prototype.hasOwnProperty.call(arr2, 'callee')) { + arr1.push.apply(arr1, arr2); + } else if (isArrayLike) { + // Otherwise loop over arr2 to prevent copying the object. + var len1 = arr1.length; + var len2 = arr2.length; + for (var j = 0; j < len2; j++) { + arr1[len1 + j] = arr2[j]; + } + } else { + arr1.push(arr2); + } + } +}; + + +/** + * Adds or removes elements from an array. This is a generic version of Array + * splice. This means that it might work on other objects similar to arrays, + * such as the arguments object. + * + * @param {Array.<T>|goog.array.ArrayLike} arr The array to modify. + * @param {number|undefined} index The index at which to start changing the + * array. If not defined, treated as 0. + * @param {number} howMany How many elements to remove (0 means no removal. A + * value below 0 is treated as zero and so is any other non number. Numbers + * are floored). + * @param {...T} var_args Optional, additional elements to insert into the + * array. + * @return {!Array.<T>} the removed elements. + * @template T + */ +goog.array.splice = function(arr, index, howMany, var_args) { + goog.asserts.assert(arr.length != null); + + return goog.array.ARRAY_PROTOTYPE_.splice.apply( + arr, goog.array.slice(arguments, 1)); +}; + + +/** + * Returns a new array from a segment of an array. This is a generic version of + * Array slice. This means that it might work on other objects similar to + * arrays, such as the arguments object. + * + * @param {Array.<T>|goog.array.ArrayLike} arr The array from + * which to copy a segment. + * @param {number} start The index of the first element to copy. + * @param {number=} opt_end The index after the last element to copy. + * @return {!Array.<T>} A new array containing the specified segment of the + * original array. + * @template T + */ +goog.array.slice = function(arr, start, opt_end) { + goog.asserts.assert(arr.length != null); + + // passing 1 arg to slice is not the same as passing 2 where the second is + // null or undefined (in that case the second argument is treated as 0). + // we could use slice on the arguments object and then use apply instead of + // testing the length + if (arguments.length <= 2) { + return goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start); + } else { + return goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start, opt_end); + } +}; + + +/** + * Removes all duplicates from an array (retaining only the first + * occurrence of each array element). This function modifies the + * array in place and doesn't change the order of the non-duplicate items. + * + * For objects, duplicates are identified as having the same unique ID as + * defined by {@link goog.getUid}. + * + * Alternatively you can specify a custom hash function that returns a unique + * value for each item in the array it should consider unique. + * + * Runtime: N, + * Worstcase space: 2N (no dupes) + * + * @param {Array.<T>|goog.array.ArrayLike} arr The array from which to remove + * duplicates. + * @param {Array=} opt_rv An optional array in which to return the results, + * instead of performing the removal inplace. If specified, the original + * array will remain unchanged. + * @param {function(T):string=} opt_hashFn An optional function to use to + * apply to every item in the array. This function should return a unique + * value for each item in the array it should consider unique. + * @template T + */ +goog.array.removeDuplicates = function(arr, opt_rv, opt_hashFn) { + var returnArray = opt_rv || arr; + var defaultHashFn = function(item) { + // Prefix each type with a single character representing the type to + // prevent conflicting keys (e.g. true and 'true'). + return goog.isObject(current) ? 'o' + goog.getUid(current) : + (typeof current).charAt(0) + current; + }; + var hashFn = opt_hashFn || defaultHashFn; + + var seen = {}, cursorInsert = 0, cursorRead = 0; + while (cursorRead < arr.length) { + var current = arr[cursorRead++]; + var key = hashFn(current); + if (!Object.prototype.hasOwnProperty.call(seen, key)) { + seen[key] = true; + returnArray[cursorInsert++] = current; + } + } + returnArray.length = cursorInsert; +}; + + +/** + * Searches the specified array for the specified target using the binary + * search algorithm. If no opt_compareFn is specified, elements are compared + * using <code>goog.array.defaultCompare</code>, which compares the elements + * using the built in < and > operators. This will produce the expected + * behavior for homogeneous arrays of String(s) and Number(s). The array + * specified <b>must</b> be sorted in ascending order (as defined by the + * comparison function). If the array is not sorted, results are undefined. + * If the array contains multiple instances of the specified target value, any + * of these instances may be found. + * + * Runtime: O(log n) + * + * @param {Array.<VALUE>|goog.array.ArrayLike} arr The array to be searched. + * @param {TARGET} target The sought value. + * @param {function(TARGET, VALUE): number=} opt_compareFn Optional comparison + * function by which the array is ordered. Should take 2 arguments to + * compare, and return a negative number, zero, or a positive number + * depending on whether the first argument is less than, equal to, or + * greater than the second. + * @return {number} Lowest index of the target value if found, otherwise + * (-(insertion point) - 1). The insertion point is where the value should + * be inserted into arr to preserve the sorted property. Return value >= 0 + * iff target is found. + * @template TARGET, VALUE + */ +goog.array.binarySearch = function(arr, target, opt_compareFn) { + return goog.array.binarySearch_(arr, + opt_compareFn || goog.array.defaultCompare, false /* isEvaluator */, + target); +}; + + +/** + * Selects an index in the specified array using the binary search algorithm. + * The evaluator receives an element and determines whether the desired index + * is before, at, or after it. The evaluator must be consistent (formally, + * goog.array.map(goog.array.map(arr, evaluator, opt_obj), goog.math.sign) + * must be monotonically non-increasing). + * + * Runtime: O(log n) + * + * @param {Array.<VALUE>|goog.array.ArrayLike} arr The array to be searched. + * @param {function(this:THIS, VALUE, number, ?): number} evaluator + * Evaluator function that receives 3 arguments (the element, the index and + * the array). Should return a negative number, zero, or a positive number + * depending on whether the desired index is before, at, or after the + * element passed to it. + * @param {THIS=} opt_obj The object to be used as the value of 'this' + * within evaluator. + * @return {number} Index of the leftmost element matched by the evaluator, if + * such exists; otherwise (-(insertion point) - 1). The insertion point is + * the index of the first element for which the evaluator returns negative, + * or arr.length if no such element exists. The return value is non-negative + * iff a match is found. + * @template THIS, VALUE + */ +goog.array.binarySelect = function(arr, evaluator, opt_obj) { + return goog.array.binarySearch_(arr, evaluator, true /* isEvaluator */, + undefined /* opt_target */, opt_obj); +}; + + +/** + * Implementation of a binary search algorithm which knows how to use both + * comparison functions and evaluators. If an evaluator is provided, will call + * the evaluator with the given optional data object, conforming to the + * interface defined in binarySelect. Otherwise, if a comparison function is + * provided, will call the comparison function against the given data object. + * + * This implementation purposefully does not use goog.bind or goog.partial for + * performance reasons. + * + * Runtime: O(log n) + * + * @param {Array.<VALUE>|goog.array.ArrayLike} arr The array to be searched. + * @param {function(TARGET, VALUE): number| + * function(this:THIS, VALUE, number, ?): number} compareFn Either an + * evaluator or a comparison function, as defined by binarySearch + * and binarySelect above. + * @param {boolean} isEvaluator Whether the function is an evaluator or a + * comparison function. + * @param {TARGET=} opt_target If the function is a comparison function, then + * this is the target to binary search for. + * @param {THIS=} opt_selfObj If the function is an evaluator, this is an + * optional this object for the evaluator. + * @return {number} Lowest index of the target value if found, otherwise + * (-(insertion point) - 1). The insertion point is where the value should + * be inserted into arr to preserve the sorted property. Return value >= 0 + * iff target is found. + * @template THIS, VALUE, TARGET + * @private + */ +goog.array.binarySearch_ = function(arr, compareFn, isEvaluator, opt_target, + opt_selfObj) { + var left = 0; // inclusive + var right = arr.length; // exclusive + var found; + while (left < right) { + var middle = (left + right) >> 1; + var compareResult; + if (isEvaluator) { + compareResult = compareFn.call(opt_selfObj, arr[middle], middle, arr); + } else { + compareResult = compareFn(opt_target, arr[middle]); + } + if (compareResult > 0) { + left = middle + 1; + } else { + right = middle; + // We are looking for the lowest index so we can't return immediately. + found = !compareResult; + } + } + // left is the index if found, or the insertion point otherwise. + // ~left is a shorthand for -left - 1. + return found ? left : ~left; +}; + + +/** + * Sorts the specified array into ascending order. If no opt_compareFn is + * specified, elements are compared using + * <code>goog.array.defaultCompare</code>, which compares the elements using + * the built in < and > operators. This will produce the expected behavior + * for homogeneous arrays of String(s) and Number(s), unlike the native sort, + * but will give unpredictable results for heterogenous lists of strings and + * numbers with different numbers of digits. + * + * This sort is not guaranteed to be stable. + * + * Runtime: Same as <code>Array.prototype.sort</code> + * + * @param {Array.<T>} arr The array to be sorted. + * @param {?function(T,T):number=} opt_compareFn Optional comparison + * function by which the + * array is to be ordered. Should take 2 arguments to compare, and return a + * negative number, zero, or a positive number depending on whether the + * first argument is less than, equal to, or greater than the second. + * @template T + */ +goog.array.sort = function(arr, opt_compareFn) { + // TODO(arv): Update type annotation since null is not accepted. + arr.sort(opt_compareFn || goog.array.defaultCompare); +}; + + +/** + * Sorts the specified array into ascending order in a stable way. If no + * opt_compareFn is specified, elements are compared using + * <code>goog.array.defaultCompare</code>, which compares the elements using + * the built in < and > operators. This will produce the expected behavior + * for homogeneous arrays of String(s) and Number(s). + * + * Runtime: Same as <code>Array.prototype.sort</code>, plus an additional + * O(n) overhead of copying the array twice. + * + * @param {Array.<T>} arr The array to be sorted. + * @param {?function(T, T): number=} opt_compareFn Optional comparison function + * by which the array is to be ordered. Should take 2 arguments to compare, + * and return a negative number, zero, or a positive number depending on + * whether the first argument is less than, equal to, or greater than the + * second. + * @template T + */ +goog.array.stableSort = function(arr, opt_compareFn) { + for (var i = 0; i < arr.length; i++) { + arr[i] = {index: i, value: arr[i]}; + } + var valueCompareFn = opt_compareFn || goog.array.defaultCompare; + function stableCompareFn(obj1, obj2) { + return valueCompareFn(obj1.value, obj2.value) || obj1.index - obj2.index; + }; + goog.array.sort(arr, stableCompareFn); + for (var i = 0; i < arr.length; i++) { + arr[i] = arr[i].value; + } +}; + + +/** + * Sorts an array of objects by the specified object key and compare + * function. If no compare function is provided, the key values are + * compared in ascending order using <code>goog.array.defaultCompare</code>. + * This won't work for keys that get renamed by the compiler. So use + * {'foo': 1, 'bar': 2} rather than {foo: 1, bar: 2}. + * @param {Array.<Object>} arr An array of objects to sort. + * @param {string} key The object key to sort by. + * @param {Function=} opt_compareFn The function to use to compare key + * values. + */ +goog.array.sortObjectsByKey = function(arr, key, opt_compareFn) { + var compare = opt_compareFn || goog.array.defaultCompare; + goog.array.sort(arr, function(a, b) { + return compare(a[key], b[key]); + }); +}; + + +/** + * Tells if the array is sorted. + * @param {!Array.<T>} arr The array. + * @param {?function(T,T):number=} opt_compareFn Function to compare the + * array elements. + * Should take 2 arguments to compare, and return a negative number, zero, + * or a positive number depending on whether the first argument is less + * than, equal to, or greater than the second. + * @param {boolean=} opt_strict If true no equal elements are allowed. + * @return {boolean} Whether the array is sorted. + * @template T + */ +goog.array.isSorted = function(arr, opt_compareFn, opt_strict) { + var compare = opt_compareFn || goog.array.defaultCompare; + for (var i = 1; i < arr.length; i++) { + var compareResult = compare(arr[i - 1], arr[i]); + if (compareResult > 0 || compareResult == 0 && opt_strict) { + return false; + } + } + return true; +}; + + +/** + * Compares two arrays for equality. Two arrays are considered equal if they + * have the same length and their corresponding elements are equal according to + * the comparison function. + * + * @param {goog.array.ArrayLike} arr1 The first array to compare. + * @param {goog.array.ArrayLike} arr2 The second array to compare. + * @param {Function=} opt_equalsFn Optional comparison function. + * Should take 2 arguments to compare, and return true if the arguments + * are equal. Defaults to {@link goog.array.defaultCompareEquality} which + * compares the elements using the built-in '===' operator. + * @return {boolean} Whether the two arrays are equal. + */ +goog.array.equals = function(arr1, arr2, opt_equalsFn) { + if (!goog.isArrayLike(arr1) || !goog.isArrayLike(arr2) || + arr1.length != arr2.length) { + return false; + } + var l = arr1.length; + var equalsFn = opt_equalsFn || goog.array.defaultCompareEquality; + for (var i = 0; i < l; i++) { + if (!equalsFn(arr1[i], arr2[i])) { + return false; + } + } + return true; +}; + + +/** + * 3-way array compare function. + * @param {!Array.<VALUE>|!goog.array.ArrayLike} arr1 The first array to + * compare. + * @param {!Array.<VALUE>|!goog.array.ArrayLike} arr2 The second array to + * compare. + * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison + * function by which the array is to be ordered. Should take 2 arguments to + * compare, and return a negative number, zero, or a positive number + * depending on whether the first argument is less than, equal to, or + * greater than the second. + * @return {number} Negative number, zero, or a positive number depending on + * whether the first argument is less than, equal to, or greater than the + * second. + * @template VALUE + */ +goog.array.compare3 = function(arr1, arr2, opt_compareFn) { + var compare = opt_compareFn || goog.array.defaultCompare; + var l = Math.min(arr1.length, arr2.length); + for (var i = 0; i < l; i++) { + var result = compare(arr1[i], arr2[i]); + if (result != 0) { + return result; + } + } + return goog.array.defaultCompare(arr1.length, arr2.length); +}; + + +/** + * Compares its two arguments for order, using the built in < and > + * operators. + * @param {VALUE} a The first object to be compared. + * @param {VALUE} b The second object to be compared. + * @return {number} A negative number, zero, or a positive number as the first + * argument is less than, equal to, or greater than the second. + * @template VALUE + */ +goog.array.defaultCompare = function(a, b) { + return a > b ? 1 : a < b ? -1 : 0; +}; + + +/** + * Compares its two arguments for equality, using the built in === operator. + * @param {*} a The first object to compare. + * @param {*} b The second object to compare. + * @return {boolean} True if the two arguments are equal, false otherwise. + */ +goog.array.defaultCompareEquality = function(a, b) { + return a === b; +}; + + +/** + * Inserts a value into a sorted array. The array is not modified if the + * value is already present. + * @param {Array.<VALUE>|goog.array.ArrayLike} array The array to modify. + * @param {VALUE} value The object to insert. + * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison + * function by which the array is ordered. Should take 2 arguments to + * compare, and return a negative number, zero, or a positive number + * depending on whether the first argument is less than, equal to, or + * greater than the second. + * @return {boolean} True if an element was inserted. + * @template VALUE + */ +goog.array.binaryInsert = function(array, value, opt_compareFn) { + var index = goog.array.binarySearch(array, value, opt_compareFn); + if (index < 0) { + goog.array.insertAt(array, value, -(index + 1)); + return true; + } + return false; +}; + + +/** + * Removes a value from a sorted array. + * @param {!Array.<VALUE>|!goog.array.ArrayLike} array The array to modify. + * @param {VALUE} value The object to remove. + * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison + * function by which the array is ordered. Should take 2 arguments to + * compare, and return a negative number, zero, or a positive number + * depending on whether the first argument is less than, equal to, or + * greater than the second. + * @return {boolean} True if an element was removed. + * @template VALUE + */ +goog.array.binaryRemove = function(array, value, opt_compareFn) { + var index = goog.array.binarySearch(array, value, opt_compareFn); + return (index >= 0) ? goog.array.removeAt(array, index) : false; +}; + + +/** + * Splits an array into disjoint buckets according to a splitting function. + * @param {Array.<T>} array The array. + * @param {function(this:S, T,number,Array.<T>):?} sorter Function to call for + * every element. This takes 3 arguments (the element, the index and the + * array) and must return a valid object key (a string, number, etc), or + * undefined, if that object should not be placed in a bucket. + * @param {S=} opt_obj The object to be used as the value of 'this' within + * sorter. + * @return {!Object} An object, with keys being all of the unique return values + * of sorter, and values being arrays containing the items for + * which the splitter returned that key. + * @template T,S + */ +goog.array.bucket = function(array, sorter, opt_obj) { + var buckets = {}; + + for (var i = 0; i < array.length; i++) { + var value = array[i]; + var key = sorter.call(opt_obj, value, i, array); + if (goog.isDef(key)) { + // Push the value to the right bucket, creating it if necessary. + var bucket = buckets[key] || (buckets[key] = []); + bucket.push(value); + } + } + + return buckets; +}; + + +/** + * Creates a new object built from the provided array and the key-generation + * function. + * @param {Array.<T>|goog.array.ArrayLike} arr Array or array like object over + * which to iterate whose elements will be the values in the new object. + * @param {?function(this:S, T, number, ?) : string} keyFunc The function to + * call for every element. This function takes 3 arguments (the element, the + * index and the array) and should return a string that will be used as the + * key for the element in the new object. If the function returns the same + * key for more than one element, the value for that key is + * implementation-defined. + * @param {S=} opt_obj The object to be used as the value of 'this' + * within keyFunc. + * @return {!Object.<T>} The new object. + * @template T,S + */ +goog.array.toObject = function(arr, keyFunc, opt_obj) { + var ret = {}; + goog.array.forEach(arr, function(element, index) { + ret[keyFunc.call(opt_obj, element, index, arr)] = element; + }); + return ret; +}; + + +/** + * Creates a range of numbers in an arithmetic progression. + * + * Range takes 1, 2, or 3 arguments: + * <pre> + * range(5) is the same as range(0, 5, 1) and produces [0, 1, 2, 3, 4] + * range(2, 5) is the same as range(2, 5, 1) and produces [2, 3, 4] + * range(-2, -5, -1) produces [-2, -3, -4] + * range(-2, -5, 1) produces [], since stepping by 1 wouldn't ever reach -5. + * </pre> + * + * @param {number} startOrEnd The starting value of the range if an end argument + * is provided. Otherwise, the start value is 0, and this is the end value. + * @param {number=} opt_end The optional end value of the range. + * @param {number=} opt_step The step size between range values. Defaults to 1 + * if opt_step is undefined or 0. + * @return {!Array.<number>} An array of numbers for the requested range. May be + * an empty array if adding the step would not converge toward the end + * value. + */ +goog.array.range = function(startOrEnd, opt_end, opt_step) { + var array = []; + var start = 0; + var end = startOrEnd; + var step = opt_step || 1; + if (opt_end !== undefined) { + start = startOrEnd; + end = opt_end; + } + + if (step * (end - start) < 0) { + // Sign mismatch: start + step will never reach the end value. + return []; + } + + if (step > 0) { + for (var i = start; i < end; i += step) { + array.push(i); + } + } else { + for (var i = start; i > end; i += step) { + array.push(i); + } + } + return array; +}; + + +/** + * Returns an array consisting of the given value repeated N times. + * + * @param {VALUE} value The value to repeat. + * @param {number} n The repeat count. + * @return {!Array.<VALUE>} An array with the repeated value. + * @template VALUE + */ +goog.array.repeat = function(value, n) { + var array = []; + for (var i = 0; i < n; i++) { + array[i] = value; + } + return array; +}; + + +/** + * Returns an array consisting of every argument with all arrays + * expanded in-place recursively. + * + * @param {...*} var_args The values to flatten. + * @return {!Array} An array containing the flattened values. + */ +goog.array.flatten = function(var_args) { + var result = []; + for (var i = 0; i < arguments.length; i++) { + var element = arguments[i]; + if (goog.isArray(element)) { + result.push.apply(result, goog.array.flatten.apply(null, element)); + } else { + result.push(element); + } + } + return result; +}; + + +/** + * Rotates an array in-place. After calling this method, the element at + * index i will be the element previously at index (i - n) % + * array.length, for all values of i between 0 and array.length - 1, + * inclusive. + * + * For example, suppose list comprises [t, a, n, k, s]. After invoking + * rotate(array, 1) (or rotate(array, -4)), array will comprise [s, t, a, n, k]. + * + * @param {!Array.<T>} array The array to rotate. + * @param {number} n The amount to rotate. + * @return {!Array.<T>} The array. + * @template T + */ +goog.array.rotate = function(array, n) { + goog.asserts.assert(array.length != null); + + if (array.length) { + n %= array.length; + if (n > 0) { + goog.array.ARRAY_PROTOTYPE_.unshift.apply(array, array.splice(-n, n)); + } else if (n < 0) { + goog.array.ARRAY_PROTOTYPE_.push.apply(array, array.splice(0, -n)); + } + } + return array; +}; + + +/** + * Moves one item of an array to a new position keeping the order of the rest + * of the items. Example use case: keeping a list of JavaScript objects + * synchronized with the corresponding list of DOM elements after one of the + * elements has been dragged to a new position. + * @param {!(Array|Arguments|{length:number})} arr The array to modify. + * @param {number} fromIndex Index of the item to move between 0 and + * {@code arr.length - 1}. + * @param {number} toIndex Target index between 0 and {@code arr.length - 1}. + */ +goog.array.moveItem = function(arr, fromIndex, toIndex) { + goog.asserts.assert(fromIndex >= 0 && fromIndex < arr.length); + goog.asserts.assert(toIndex >= 0 && toIndex < arr.length); + // Remove 1 item at fromIndex. + var removedItems = goog.array.ARRAY_PROTOTYPE_.splice.call(arr, fromIndex, 1); + // Insert the removed item at toIndex. + goog.array.ARRAY_PROTOTYPE_.splice.call(arr, toIndex, 0, removedItems[0]); + // We don't use goog.array.insertAt and goog.array.removeAt, because they're + // significantly slower than splice. +}; + + +/** + * Creates a new array for which the element at position i is an array of the + * ith element of the provided arrays. The returned array will only be as long + * as the shortest array provided; additional values are ignored. For example, + * the result of zipping [1, 2] and [3, 4, 5] is [[1,3], [2, 4]]. + * + * This is similar to the zip() function in Python. See {@link + * http://docs.python.org/library/functions.html#zip} + * + * @param {...!goog.array.ArrayLike} var_args Arrays to be combined. + * @return {!Array.<!Array>} A new array of arrays created from provided arrays. + */ +goog.array.zip = function(var_args) { + if (!arguments.length) { + return []; + } + var result = []; + for (var i = 0; true; i++) { + var value = []; + for (var j = 0; j < arguments.length; j++) { + var arr = arguments[j]; + // If i is larger than the array length, this is the shortest array. + if (i >= arr.length) { + return result; + } + value.push(arr[i]); + } + result.push(value); + } +}; + + +/** + * Shuffles the values in the specified array using the Fisher-Yates in-place + * shuffle (also known as the Knuth Shuffle). By default, calls Math.random() + * and so resets the state of that random number generator. Similarly, may reset + * the state of the any other specified random number generator. + * + * Runtime: O(n) + * + * @param {!Array} arr The array to be shuffled. + * @param {function():number=} opt_randFn Optional random function to use for + * shuffling. + * Takes no arguments, and returns a random number on the interval [0, 1). + * Defaults to Math.random() using JavaScript's built-in Math library. + */ +goog.array.shuffle = function(arr, opt_randFn) { + var randFn = opt_randFn || Math.random; + + for (var i = arr.length - 1; i > 0; i--) { + // Choose a random array index in [0, i] (inclusive with i). + var j = Math.floor(randFn() * (i + 1)); + + var tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/asserts/asserts.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/asserts/asserts.js new file mode 100644 index 0000000..2bb985f --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/asserts/asserts.js @@ -0,0 +1,344 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utilities to check the preconditions, postconditions and + * invariants runtime. + * + * Methods in this package should be given special treatment by the compiler + * for type-inference. For example, <code>goog.asserts.assert(foo)</code> + * will restrict <code>foo</code> to a truthy value. + * + * The compiler has an option to disable asserts. So code like: + * <code> + * var x = goog.asserts.assert(foo()); goog.asserts.assert(bar()); + * </code> + * will be transformed into: + * <code> + * var x = foo(); + * </code> + * The compiler will leave in foo() (because its return value is used), + * but it will remove bar() because it assumes it does not have side-effects. + * + */ + +goog.provide('goog.asserts'); +goog.provide('goog.asserts.AssertionError'); + +goog.require('goog.debug.Error'); +goog.require('goog.dom.NodeType'); +goog.require('goog.string'); + + +/** + * @define {boolean} Whether to strip out asserts or to leave them in. + */ +goog.define('goog.asserts.ENABLE_ASSERTS', goog.DEBUG); + + + +/** + * Error object for failed assertions. + * @param {string} messagePattern The pattern that was used to form message. + * @param {!Array.<*>} messageArgs The items to substitute into the pattern. + * @constructor + * @extends {goog.debug.Error} + * @final + */ +goog.asserts.AssertionError = function(messagePattern, messageArgs) { + messageArgs.unshift(messagePattern); + goog.debug.Error.call(this, goog.string.subs.apply(null, messageArgs)); + // Remove the messagePattern afterwards to avoid permenantly modifying the + // passed in array. + messageArgs.shift(); + + /** + * The message pattern used to format the error message. Error handlers can + * use this to uniquely identify the assertion. + * @type {string} + */ + this.messagePattern = messagePattern; +}; +goog.inherits(goog.asserts.AssertionError, goog.debug.Error); + + +/** @override */ +goog.asserts.AssertionError.prototype.name = 'AssertionError'; + + +/** + * The default error handler. + * @param {!goog.asserts.AssertionError} e The exception to be handled. + */ +goog.asserts.DEFAULT_ERROR_HANDLER = function(e) { throw e; }; + + +/** + * The handler responsible for throwing or logging assertion errors. + * @private {function(!goog.asserts.AssertionError)} + */ +goog.asserts.errorHandler_ = goog.asserts.DEFAULT_ERROR_HANDLER; + + +/** + * Throws an exception with the given message and "Assertion failed" prefixed + * onto it. + * @param {string} defaultMessage The message to use if givenMessage is empty. + * @param {Array.<*>} defaultArgs The substitution arguments for defaultMessage. + * @param {string|undefined} givenMessage Message supplied by the caller. + * @param {Array.<*>} givenArgs The substitution arguments for givenMessage. + * @throws {goog.asserts.AssertionError} When the value is not a number. + * @private + */ +goog.asserts.doAssertFailure_ = + function(defaultMessage, defaultArgs, givenMessage, givenArgs) { + var message = 'Assertion failed'; + if (givenMessage) { + message += ': ' + givenMessage; + var args = givenArgs; + } else if (defaultMessage) { + message += ': ' + defaultMessage; + args = defaultArgs; + } + // The '' + works around an Opera 10 bug in the unit tests. Without it, + // a stack trace is added to var message above. With this, a stack trace is + // not added until this line (it causes the extra garbage to be added after + // the assertion message instead of in the middle of it). + var e = new goog.asserts.AssertionError('' + message, args || []); + goog.asserts.errorHandler_(e); +}; + + +/** + * Sets a custom error handler that can be used to customize the behavior of + * assertion failures, for example by turning all assertion failures into log + * messages. + * @param {function(goog.asserts.AssertionError)} errorHandler + */ +goog.asserts.setErrorHandler = function(errorHandler) { + if (goog.asserts.ENABLE_ASSERTS) { + goog.asserts.errorHandler_ = errorHandler; + } +}; + + +/** + * Checks if the condition evaluates to true if goog.asserts.ENABLE_ASSERTS is + * true. + * @template T + * @param {T} condition The condition to check. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @return {T} The value of the condition. + * @throws {goog.asserts.AssertionError} When the condition evaluates to false. + */ +goog.asserts.assert = function(condition, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && !condition) { + goog.asserts.doAssertFailure_('', null, opt_message, + Array.prototype.slice.call(arguments, 2)); + } + return condition; +}; + + +/** + * Fails if goog.asserts.ENABLE_ASSERTS is true. This function is useful in case + * when we want to add a check in the unreachable area like switch-case + * statement: + * + * <pre> + * switch(type) { + * case FOO: doSomething(); break; + * case BAR: doSomethingElse(); break; + * default: goog.assert.fail('Unrecognized type: ' + type); + * // We have only 2 types - "default:" section is unreachable code. + * } + * </pre> + * + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @throws {goog.asserts.AssertionError} Failure. + */ +goog.asserts.fail = function(opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS) { + goog.asserts.errorHandler_(new goog.asserts.AssertionError( + 'Failure' + (opt_message ? ': ' + opt_message : ''), + Array.prototype.slice.call(arguments, 1))); + } +}; + + +/** + * Checks if the value is a number if goog.asserts.ENABLE_ASSERTS is true. + * @param {*} value The value to check. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @return {number} The value, guaranteed to be a number when asserts enabled. + * @throws {goog.asserts.AssertionError} When the value is not a number. + */ +goog.asserts.assertNumber = function(value, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && !goog.isNumber(value)) { + goog.asserts.doAssertFailure_('Expected number but got %s: %s.', + [goog.typeOf(value), value], opt_message, + Array.prototype.slice.call(arguments, 2)); + } + return /** @type {number} */ (value); +}; + + +/** + * Checks if the value is a string if goog.asserts.ENABLE_ASSERTS is true. + * @param {*} value The value to check. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @return {string} The value, guaranteed to be a string when asserts enabled. + * @throws {goog.asserts.AssertionError} When the value is not a string. + */ +goog.asserts.assertString = function(value, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && !goog.isString(value)) { + goog.asserts.doAssertFailure_('Expected string but got %s: %s.', + [goog.typeOf(value), value], opt_message, + Array.prototype.slice.call(arguments, 2)); + } + return /** @type {string} */ (value); +}; + + +/** + * Checks if the value is a function if goog.asserts.ENABLE_ASSERTS is true. + * @param {*} value The value to check. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @return {!Function} The value, guaranteed to be a function when asserts + * enabled. + * @throws {goog.asserts.AssertionError} When the value is not a function. + */ +goog.asserts.assertFunction = function(value, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && !goog.isFunction(value)) { + goog.asserts.doAssertFailure_('Expected function but got %s: %s.', + [goog.typeOf(value), value], opt_message, + Array.prototype.slice.call(arguments, 2)); + } + return /** @type {!Function} */ (value); +}; + + +/** + * Checks if the value is an Object if goog.asserts.ENABLE_ASSERTS is true. + * @param {*} value The value to check. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @return {!Object} The value, guaranteed to be a non-null object. + * @throws {goog.asserts.AssertionError} When the value is not an object. + */ +goog.asserts.assertObject = function(value, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && !goog.isObject(value)) { + goog.asserts.doAssertFailure_('Expected object but got %s: %s.', + [goog.typeOf(value), value], + opt_message, Array.prototype.slice.call(arguments, 2)); + } + return /** @type {!Object} */ (value); +}; + + +/** + * Checks if the value is an Array if goog.asserts.ENABLE_ASSERTS is true. + * @param {*} value The value to check. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @return {!Array} The value, guaranteed to be a non-null array. + * @throws {goog.asserts.AssertionError} When the value is not an array. + */ +goog.asserts.assertArray = function(value, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && !goog.isArray(value)) { + goog.asserts.doAssertFailure_('Expected array but got %s: %s.', + [goog.typeOf(value), value], opt_message, + Array.prototype.slice.call(arguments, 2)); + } + return /** @type {!Array} */ (value); +}; + + +/** + * Checks if the value is a boolean if goog.asserts.ENABLE_ASSERTS is true. + * @param {*} value The value to check. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @return {boolean} The value, guaranteed to be a boolean when asserts are + * enabled. + * @throws {goog.asserts.AssertionError} When the value is not a boolean. + */ +goog.asserts.assertBoolean = function(value, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && !goog.isBoolean(value)) { + goog.asserts.doAssertFailure_('Expected boolean but got %s: %s.', + [goog.typeOf(value), value], opt_message, + Array.prototype.slice.call(arguments, 2)); + } + return /** @type {boolean} */ (value); +}; + + +/** + * Checks if the value is a DOM Element if goog.asserts.ENABLE_ASSERTS is true. + * @param {*} value The value to check. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @return {!Element} The value, likely to be a DOM Element when asserts are + * enabled. + * @throws {goog.asserts.AssertionError} When the value is not a boolean. + */ +goog.asserts.assertElement = function(value, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && (!goog.isObject(value) || + value.nodeType != goog.dom.NodeType.ELEMENT)) { + goog.asserts.doAssertFailure_('Expected Element but got %s: %s.', + [goog.typeOf(value), value], opt_message, + Array.prototype.slice.call(arguments, 2)); + } + return /** @type {!Element} */ (value); +}; + + +/** + * Checks if the value is an instance of the user-defined type if + * goog.asserts.ENABLE_ASSERTS is true. + * + * The compiler may tighten the type returned by this function. + * + * @param {*} value The value to check. + * @param {function(new: T, ...)} type A user-defined constructor. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @throws {goog.asserts.AssertionError} When the value is not an instance of + * type. + * @return {!T} + * @template T + */ +goog.asserts.assertInstanceof = function(value, type, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && !(value instanceof type)) { + goog.asserts.doAssertFailure_('instanceof check failed.', null, + opt_message, Array.prototype.slice.call(arguments, 3)); + } + return value; +}; + + +/** + * Checks that no enumerable keys are present in Object.prototype. Such keys + * would break most code that use {@code for (var ... in ...)} loops. + */ +goog.asserts.assertObjectPrototypeIsIntact = function() { + for (var key in Object.prototype) { + goog.asserts.fail(key + ' should not be enumerable in Object.prototype.'); + } +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/async/delay.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/async/delay.js new file mode 100644 index 0000000..1c5916a --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/async/delay.js @@ -0,0 +1,179 @@ +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Defines a class useful for handling functions that must be + * invoked after a delay, especially when that delay is frequently restarted. + * Examples include delaying before displaying a tooltip, menu hysteresis, + * idle timers, etc. + * @author brenneman@google.com (Shawn Brenneman) + * @see ../demos/timers.html + */ + + +goog.provide('goog.Delay'); +goog.provide('goog.async.Delay'); + +goog.require('goog.Disposable'); +goog.require('goog.Timer'); + + + +/** + * A Delay object invokes the associated function after a specified delay. The + * interval duration can be specified once in the constructor, or can be defined + * each time the delay is started. Calling start on an active delay will reset + * the timer. + * + * @param {Function} listener Function to call when the delay completes. + * @param {number=} opt_interval The default length of the invocation delay (in + * milliseconds). + * @param {Object=} opt_handler The object scope to invoke the function in. + * @constructor + * @extends {goog.Disposable} + * @final + */ +goog.async.Delay = function(listener, opt_interval, opt_handler) { + goog.Disposable.call(this); + + /** + * The function that will be invoked after a delay. + * @type {Function} + * @private + */ + this.listener_ = listener; + + /** + * The default amount of time to delay before invoking the callback. + * @type {number} + * @private + */ + this.interval_ = opt_interval || 0; + + /** + * The object context to invoke the callback in. + * @type {Object|undefined} + * @private + */ + this.handler_ = opt_handler; + + + /** + * Cached callback function invoked when the delay finishes. + * @type {Function} + * @private + */ + this.callback_ = goog.bind(this.doAction_, this); +}; +goog.inherits(goog.async.Delay, goog.Disposable); + + + +/** + * A deprecated alias. + * @deprecated Use goog.async.Delay instead. + * @constructor + * @final + */ +goog.Delay = goog.async.Delay; + + +/** + * Identifier of the active delay timeout, or 0 when inactive. + * @type {number} + * @private + */ +goog.async.Delay.prototype.id_ = 0; + + +/** + * Disposes of the object, cancelling the timeout if it is still outstanding and + * removing all object references. + * @override + * @protected + */ +goog.async.Delay.prototype.disposeInternal = function() { + goog.async.Delay.superClass_.disposeInternal.call(this); + this.stop(); + delete this.listener_; + delete this.handler_; +}; + + +/** + * Starts the delay timer. The provided listener function will be called after + * the specified interval. Calling start on an active timer will reset the + * delay interval. + * @param {number=} opt_interval If specified, overrides the object's default + * interval with this one (in milliseconds). + */ +goog.async.Delay.prototype.start = function(opt_interval) { + this.stop(); + this.id_ = goog.Timer.callOnce( + this.callback_, + goog.isDef(opt_interval) ? opt_interval : this.interval_); +}; + + +/** + * Stops the delay timer if it is active. No action is taken if the timer is not + * in use. + */ +goog.async.Delay.prototype.stop = function() { + if (this.isActive()) { + goog.Timer.clear(this.id_); + } + this.id_ = 0; +}; + + +/** + * Fires delay's action even if timer has already gone off or has not been + * started yet; guarantees action firing. Stops the delay timer. + */ +goog.async.Delay.prototype.fire = function() { + this.stop(); + this.doAction_(); +}; + + +/** + * Fires delay's action only if timer is currently active. Stops the delay + * timer. + */ +goog.async.Delay.prototype.fireIfActive = function() { + if (this.isActive()) { + this.fire(); + } +}; + + +/** + * @return {boolean} True if the delay is currently active, false otherwise. + */ +goog.async.Delay.prototype.isActive = function() { + return this.id_ != 0; +}; + + +/** + * Invokes the callback function after the delay successfully completes. + * @private + */ +goog.async.Delay.prototype.doAction_ = function() { + this.id_ = 0; + if (this.listener_) { + this.listener_.call(this.handler_); + } +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/async/nexttick.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/async/nexttick.js new file mode 100644 index 0000000..ad2cb9e --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/async/nexttick.js @@ -0,0 +1,203 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provides a function to schedule running a function as soon + * as possible after the current JS execution stops and yields to the event + * loop. + * + */ + +goog.provide('goog.async.nextTick'); +goog.provide('goog.async.throwException'); + +goog.require('goog.debug.entryPointRegistry'); +goog.require('goog.functions'); + + +/** + * Throw an item without interrupting the current execution context. For + * example, if processing a group of items in a loop, sometimes it is useful + * to report an error while still allowing the rest of the batch to be + * processed. + * @param {*} exception + */ +goog.async.throwException = function(exception) { + // Each throw needs to be in its own context. + goog.global.setTimeout(function() { throw exception; }, 0); +}; + + +/** + * Fires the provided callbacks as soon as possible after the current JS + * execution context. setTimeout(…, 0) always takes at least 5ms for legacy + * reasons. + * + * This will not schedule the callback as a microtask (i.e. a task that can + * preempt user input or networking callbacks). It is meant to emulate what + * setTimeout(_, 0) would do if it were not throttled. If you desire microtask + * behavior, use {@see goog.Promise} instead. + * + * @param {function(this:SCOPE)} callback Callback function to fire as soon as + * possible. + * @param {SCOPE=} opt_context Object in whose scope to call the listener. + * @template SCOPE + */ +goog.async.nextTick = function(callback, opt_context) { + var cb = callback; + if (opt_context) { + cb = goog.bind(callback, opt_context); + } + cb = goog.async.nextTick.wrapCallback_(cb); + // Introduced and currently only supported by IE10. + if (goog.isFunction(goog.global.setImmediate)) { + goog.global.setImmediate(cb); + return; + } + // Look for and cache the custom fallback version of setImmediate. + if (!goog.async.nextTick.setImmediate_) { + goog.async.nextTick.setImmediate_ = + goog.async.nextTick.getSetImmediateEmulator_(); + } + goog.async.nextTick.setImmediate_(cb); +}; + + +/** + * Cache for the setImmediate implementation. + * @type {function(function())} + * @private + */ +goog.async.nextTick.setImmediate_; + + +/** + * Determines the best possible implementation to run a function as soon as + * the JS event loop is idle. + * @return {function(function())} The "setImmediate" implementation. + * @private + */ +goog.async.nextTick.getSetImmediateEmulator_ = function() { + // Create a private message channel and use it to postMessage empty messages + // to ourselves. + var Channel = goog.global['MessageChannel']; + // If MessageChannel is not available and we are in a browser, implement + // an iframe based polyfill in browsers that have postMessage and + // document.addEventListener. The latter excludes IE8 because it has a + // synchronous postMessage implementation. + if (typeof Channel === 'undefined' && typeof window !== 'undefined' && + window.postMessage && window.addEventListener) { + /** @constructor */ + Channel = function() { + // Make an empty, invisible iframe. + var iframe = document.createElement('iframe'); + iframe.style.display = 'none'; + iframe.src = ''; + document.documentElement.appendChild(iframe); + var win = iframe.contentWindow; + var doc = win.document; + doc.open(); + doc.write(''); + doc.close(); + // Do not post anything sensitive over this channel, as the workaround for + // pages with file: origin could allow that information to be modified or + // intercepted. + var message = 'callImmediate' + Math.random(); + // The same origin policy rejects attempts to postMessage from file: urls + // unless the origin is '*'. + // TODO(b/16335441): Use '*' origin for data: and other similar protocols. + var origin = win.location.protocol == 'file:' ? + '*' : win.location.protocol + '//' + win.location.host; + var onmessage = goog.bind(function(e) { + // Validate origin and message to make sure that this message was + // intended for us. + if (e.origin != origin && e.data != message) { + return; + } + this['port1'].onmessage(); + }, this); + win.addEventListener('message', onmessage, false); + this['port1'] = {}; + this['port2'] = { + postMessage: function() { + win.postMessage(message, origin); + } + }; + }; + } + if (typeof Channel !== 'undefined') { + var channel = new Channel(); + // Use a fifo linked list to call callbacks in the right order. + var head = {}; + var tail = head; + channel['port1'].onmessage = function() { + head = head.next; + var cb = head.cb; + head.cb = null; + cb(); + }; + return function(cb) { + tail.next = { + cb: cb + }; + tail = tail.next; + channel['port2'].postMessage(0); + }; + } + // Implementation for IE6-8: Script elements fire an asynchronous + // onreadystatechange event when inserted into the DOM. + if (typeof document !== 'undefined' && 'onreadystatechange' in + document.createElement('script')) { + return function(cb) { + var script = document.createElement('script'); + script.onreadystatechange = function() { + // Clean up and call the callback. + script.onreadystatechange = null; + script.parentNode.removeChild(script); + script = null; + cb(); + cb = null; + }; + document.documentElement.appendChild(script); + }; + } + // Fall back to setTimeout with 0. In browsers this creates a delay of 5ms + // or more. + return function(cb) { + goog.global.setTimeout(cb, 0); + }; +}; + + +/** + * Helper function that is overrided to protect callbacks with entry point + * monitor if the application monitors entry points. + * @param {function()} callback Callback function to fire as soon as possible. + * @return {function()} The wrapped callback. + * @private + */ +goog.async.nextTick.wrapCallback_ = goog.functions.identity; + + +// Register the callback function as an entry point, so that it can be +// monitored for exception handling, etc. This has to be done in this file +// since it requires special code to handle all browsers. +goog.debug.entryPointRegistry.register( + /** + * @param {function(!Function): !Function} transformer The transforming + * function. + */ + function(transformer) { + goog.async.nextTick.wrapCallback_ = transformer; + }); diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/async/run.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/async/run.js new file mode 100644 index 0000000..0010752 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/async/run.js @@ -0,0 +1,150 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +goog.provide('goog.async.run'); + +goog.require('goog.async.nextTick'); +goog.require('goog.async.throwException'); +goog.require('goog.testing.watchers'); + + +/** + * Fires the provided callback just before the current callstack unwinds, or as + * soon as possible after the current JS execution context. + * @param {function(this:THIS)} callback + * @param {THIS=} opt_context Object to use as the "this value" when calling + * the provided function. + * @template THIS + */ +goog.async.run = function(callback, opt_context) { + if (!goog.async.run.schedule_) { + goog.async.run.initializeRunner_(); + } + if (!goog.async.run.workQueueScheduled_) { + // Nothing is currently scheduled, schedule it now. + goog.async.run.schedule_(); + goog.async.run.workQueueScheduled_ = true; + } + + goog.async.run.workQueue_.push( + new goog.async.run.WorkItem_(callback, opt_context)); +}; + + +/** + * Initializes the function to use to process the work queue. + * @private + */ +goog.async.run.initializeRunner_ = function() { + // If native Promises are available in the browser, just schedule the callback + // on a fulfilled promise, which is specified to be async, but as fast as + // possible. + if (goog.global.Promise && goog.global.Promise.resolve) { + var promise = goog.global.Promise.resolve(); + goog.async.run.schedule_ = function() { + promise.then(goog.async.run.processWorkQueue); + }; + } else { + goog.async.run.schedule_ = function() { + goog.async.nextTick(goog.async.run.processWorkQueue); + }; + } +}; + + +/** + * Forces goog.async.run to use nextTick instead of Promise. + * + * This should only be done in unit tests. It's useful because MockClock + * replaces nextTick, but not the browser Promise implementation, so it allows + * Promise-based code to be tested with MockClock. + */ +goog.async.run.forceNextTick = function() { + goog.async.run.schedule_ = function() { + goog.async.nextTick(goog.async.run.processWorkQueue); + }; +}; + + +/** + * The function used to schedule work asynchronousely. + * @private {function()} + */ +goog.async.run.schedule_; + + +/** @private {boolean} */ +goog.async.run.workQueueScheduled_ = false; + + +/** @private {!Array.<!goog.async.run.WorkItem_>} */ +goog.async.run.workQueue_ = []; + + +if (goog.DEBUG) { + /** + * Reset the event queue. + * @private + */ + goog.async.run.resetQueue_ = function() { + goog.async.run.workQueueScheduled_ = false; + goog.async.run.workQueue_ = []; + }; + + // If there is a clock implemenation in use for testing + // and it is reset, reset the queue. + goog.testing.watchers.watchClockReset(goog.async.run.resetQueue_); +} + + +/** + * Run any pending goog.async.run work items. This function is not intended + * for general use, but for use by entry point handlers to run items ahead of + * goog.async.nextTick. + */ +goog.async.run.processWorkQueue = function() { + // NOTE: additional work queue items may be pushed while processing. + while (goog.async.run.workQueue_.length) { + // Don't let the work queue grow indefinitely. + var workItems = goog.async.run.workQueue_; + goog.async.run.workQueue_ = []; + for (var i = 0; i < workItems.length; i++) { + var workItem = workItems[i]; + try { + workItem.fn.call(workItem.scope); + } catch (e) { + goog.async.throwException(e); + } + } + } + + // There are no more work items, reset the work queue. + goog.async.run.workQueueScheduled_ = false; +}; + + + +/** + * @constructor + * @final + * @struct + * @private + * + * @param {function()} fn + * @param {Object|null|undefined} scope + */ +goog.async.run.WorkItem_ = function(fn, scope) { + /** @const */ this.fn = fn; + /** @const */ this.scope = scope; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/base.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/base.js new file mode 100644 index 0000000..ead806b --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/base.js @@ -0,0 +1,1875 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Bootstrap for the Google JS Library (Closure). + * + * In uncompiled mode base.js will write out Closure's deps file, unless the + * global <code>CLOSURE_NO_DEPS</code> is set to true. This allows projects to + * include their own deps file(s) from different locations. + * + * + * @provideGoog + */ + + +/** + * @define {boolean} Overridden to true by the compiler when --closure_pass + * or --mark_as_compiled is specified. + */ +var COMPILED = false; + + +/** + * Base namespace for the Closure library. Checks to see goog is already + * defined in the current scope before assigning to prevent clobbering if + * base.js is loaded more than once. + * + * @const + */ +var goog = goog || {}; + + +/** + * Reference to the global context. In most cases this will be 'window'. + */ +goog.global = this; + + +/** + * A hook for overriding the define values in uncompiled mode. + * + * In uncompiled mode, {@code CLOSURE_UNCOMPILED_DEFINES} may be defined before + * loading base.js. If a key is defined in {@code CLOSURE_UNCOMPILED_DEFINES}, + * {@code goog.define} will use the value instead of the default value. This + * allows flags to be overwritten without compilation (this is normally + * accomplished with the compiler's "define" flag). + * + * Example: + * <pre> + * var CLOSURE_UNCOMPILED_DEFINES = {'goog.DEBUG': false}; + * </pre> + * + * @type {Object.<string, (string|number|boolean)>|undefined} + */ +goog.global.CLOSURE_UNCOMPILED_DEFINES; + + +/** + * A hook for overriding the define values in uncompiled or compiled mode, + * like CLOSURE_UNCOMPILED_DEFINES but effective in compiled code. In + * uncompiled code CLOSURE_UNCOMPILED_DEFINES takes precedence. + * + * Also unlike CLOSURE_UNCOMPILED_DEFINES the values must be number, boolean or + * string literals or the compiler will emit an error. + * + * While any @define value may be set, only those set with goog.define will be + * effective for uncompiled code. + * + * Example: + * <pre> + * var CLOSURE_DEFINES = {'goog.DEBUG': false}; + * </pre> + * + * @type {Object.<string, (string|number|boolean)>|undefined} + */ +goog.global.CLOSURE_DEFINES; + + +/** + * Returns true if the specified value is not undefined. + * WARNING: Do not use this to test if an object has a property. Use the in + * operator instead. + * + * @param {?} val Variable to test. + * @return {boolean} Whether variable is defined. + */ +goog.isDef = function(val) { + // void 0 always evaluates to undefined and hence we do not need to depend on + // the definition of the global variable named 'undefined'. + return val !== void 0; +}; + + +/** + * Builds an object structure for the provided namespace path, ensuring that + * names that already exist are not overwritten. For example: + * "a.b.c" -> a = {};a.b={};a.b.c={}; + * Used by goog.provide and goog.exportSymbol. + * @param {string} name name of the object that this file defines. + * @param {*=} opt_object the object to expose at the end of the path. + * @param {Object=} opt_objectToExportTo The object to add the path to; default + * is |goog.global|. + * @private + */ +goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) { + var parts = name.split('.'); + var cur = opt_objectToExportTo || goog.global; + + // Internet Explorer exhibits strange behavior when throwing errors from + // methods externed in this manner. See the testExportSymbolExceptions in + // base_test.html for an example. + if (!(parts[0] in cur) && cur.execScript) { + cur.execScript('var ' + parts[0]); + } + + // Certain browsers cannot parse code in the form for((a in b); c;); + // This pattern is produced by the JSCompiler when it collapses the + // statement above into the conditional loop below. To prevent this from + // happening, use a for-loop and reserve the init logic as below. + + // Parentheses added to eliminate strict JS warning in Firefox. + for (var part; parts.length && (part = parts.shift());) { + if (!parts.length && goog.isDef(opt_object)) { + // last part and we have an object; use it + cur[part] = opt_object; + } else if (cur[part]) { + cur = cur[part]; + } else { + cur = cur[part] = {}; + } + } +}; + + +/** + * Defines a named value. In uncompiled mode, the value is retreived from + * CLOSURE_DEFINES or CLOSURE_UNCOMPILED_DEFINES if the object is defined and + * has the property specified, and otherwise used the defined defaultValue. + * When compiled, the default can be overridden using compiler command-line + * options. + * + * @param {string} name The distinguished name to provide. + * @param {string|number|boolean} defaultValue + */ +goog.define = function(name, defaultValue) { + var value = defaultValue; + if (!COMPILED) { + if (goog.global.CLOSURE_UNCOMPILED_DEFINES && + Object.prototype.hasOwnProperty.call( + goog.global.CLOSURE_UNCOMPILED_DEFINES, name)) { + value = goog.global.CLOSURE_UNCOMPILED_DEFINES[name]; + } else if (goog.global.CLOSURE_DEFINES && + Object.prototype.hasOwnProperty.call( + goog.global.CLOSURE_DEFINES, name)) { + value = goog.global.CLOSURE_DEFINES[name]; + } + } + goog.exportPath_(name, value); +}; + + +/** + * @define {boolean} DEBUG is provided as a convenience so that debugging code + * that should not be included in a production js_binary can be easily stripped + * by specifying --define goog.DEBUG=false to the JSCompiler. For example, most + * toString() methods should be declared inside an "if (goog.DEBUG)" conditional + * because they are generally used for debugging purposes and it is difficult + * for the JSCompiler to statically determine whether they are used. + */ +goog.DEBUG = true; + + +/** + * @define {string} LOCALE defines the locale being used for compilation. It is + * used to select locale specific data to be compiled in js binary. BUILD rule + * can specify this value by "--define goog.LOCALE=<locale_name>" as JSCompiler + * option. + * + * Take into account that the locale code format is important. You should use + * the canonical Unicode format with hyphen as a delimiter. Language must be + * lowercase, Language Script - Capitalized, Region - UPPERCASE. + * There are few examples: pt-BR, en, en-US, sr-Latin-BO, zh-Hans-CN. + * + * See more info about locale codes here: + * http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers + * + * For language codes you should use values defined by ISO 693-1. See it here + * http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from + * this rule: the Hebrew language. For legacy reasons the old code (iw) should + * be used instead of the new code (he), see http://wiki/Main/IIISynonyms. + */ +goog.define('goog.LOCALE', 'en'); // default to en + + +/** + * @define {boolean} Whether this code is running on trusted sites. + * + * On untrusted sites, several native functions can be defined or overridden by + * external libraries like Prototype, Datejs, and JQuery and setting this flag + * to false forces closure to use its own implementations when possible. + * + * If your JavaScript can be loaded by a third party site and you are wary about + * relying on non-standard implementations, specify + * "--define goog.TRUSTED_SITE=false" to the JSCompiler. + */ +goog.define('goog.TRUSTED_SITE', true); + + +/** + * @define {boolean} Whether a project is expected to be running in strict mode. + * + * This define can be used to trigger alternate implementations compatible with + * running in EcmaScript Strict mode or warn about unavailable functionality. + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/Strict_mode + */ +goog.define('goog.STRICT_MODE_COMPATIBLE', false); + + +/** + * Creates object stubs for a namespace. The presence of one or more + * goog.provide() calls indicate that the file defines the given + * objects/namespaces. Provided objects must not be null or undefined. + * Build tools also scan for provide/require statements + * to discern dependencies, build dependency files (see deps.js), etc. + * @see goog.require + * @param {string} name Namespace provided by this file in the form + * "goog.package.part". + */ +goog.provide = function(name) { + if (!COMPILED) { + // Ensure that the same namespace isn't provided twice. This is intended + // to teach new developers that 'goog.provide' is effectively a variable + // declaration. And when JSCompiler transforms goog.provide into a real + // variable declaration, the compiled JS should work the same as the raw + // JS--even when the raw JS uses goog.provide incorrectly. + if (goog.isProvided_(name)) { + throw Error('Namespace "' + name + '" already declared.'); + } + delete goog.implicitNamespaces_[name]; + + var namespace = name; + while ((namespace = namespace.substring(0, namespace.lastIndexOf('.')))) { + if (goog.getObjectByName(namespace)) { + break; + } + goog.implicitNamespaces_[namespace] = true; + } + } + + goog.exportPath_(name); +}; + + +/** + * Marks that the current file should only be used for testing, and never for + * live code in production. + * + * In the case of unit tests, the message may optionally be an exact namespace + * for the test (e.g. 'goog.stringTest'). The linter will then ignore the extra + * provide (if not explicitly defined in the code). + * + * @param {string=} opt_message Optional message to add to the error that's + * raised when used in production code. + */ +goog.setTestOnly = function(opt_message) { + if (COMPILED && !goog.DEBUG) { + opt_message = opt_message || ''; + throw Error('Importing test-only code into non-debug environment' + + (opt_message ? ': ' + opt_message : '.')); + } +}; + + +/** + * Forward declares a symbol. This is an indication to the compiler that the + * symbol may be used in the source yet is not required and may not be provided + * in compilation. + * + * The most common usage of forward declaration is code that takes a type as a + * function parameter but does not need to require it. By forward declaring + * instead of requiring, no hard dependency is made, and (if not required + * elsewhere) the namespace may never be required and thus, not be pulled + * into the JavaScript binary. If it is required elsewhere, it will be type + * checked as normal. + * + * + * @param {string} name The namespace to forward declare in the form of + * "goog.package.part". + */ +goog.forwardDeclare = function(name) {}; + + +if (!COMPILED) { + + /** + * Check if the given name has been goog.provided. This will return false for + * names that are available only as implicit namespaces. + * @param {string} name name of the object to look for. + * @return {boolean} Whether the name has been provided. + * @private + */ + goog.isProvided_ = function(name) { + return !goog.implicitNamespaces_[name] && + goog.isDefAndNotNull(goog.getObjectByName(name)); + }; + + /** + * Namespaces implicitly defined by goog.provide. For example, + * goog.provide('goog.events.Event') implicitly declares that 'goog' and + * 'goog.events' must be namespaces. + * + * @type {Object} + * @private + */ + goog.implicitNamespaces_ = {}; +} + + +/** + * Returns an object based on its fully qualified external name. The object + * is not found if null or undefined. If you are using a compilation pass that + * renames property names beware that using this function will not find renamed + * properties. + * + * @param {string} name The fully qualified name. + * @param {Object=} opt_obj The object within which to look; default is + * |goog.global|. + * @return {?} The value (object or primitive) or, if not found, null. + */ +goog.getObjectByName = function(name, opt_obj) { + var parts = name.split('.'); + var cur = opt_obj || goog.global; + for (var part; part = parts.shift(); ) { + if (goog.isDefAndNotNull(cur[part])) { + cur = cur[part]; + } else { + return null; + } + } + return cur; +}; + + +/** + * Globalizes a whole namespace, such as goog or goog.lang. + * + * @param {Object} obj The namespace to globalize. + * @param {Object=} opt_global The object to add the properties to. + * @deprecated Properties may be explicitly exported to the global scope, but + * this should no longer be done in bulk. + */ +goog.globalize = function(obj, opt_global) { + var global = opt_global || goog.global; + for (var x in obj) { + global[x] = obj[x]; + } +}; + + +/** + * Adds a dependency from a file to the files it requires. + * @param {string} relPath The path to the js file. + * @param {Array} provides An array of strings with the names of the objects + * this file provides. + * @param {Array} requires An array of strings with the names of the objects + * this file requires. + */ +goog.addDependency = function(relPath, provides, requires) { + if (goog.DEPENDENCIES_ENABLED) { + var provide, require; + var path = relPath.replace(/\\/g, '/'); + var deps = goog.dependencies_; + for (var i = 0; provide = provides[i]; i++) { + deps.nameToPath[provide] = path; + if (!(path in deps.pathToNames)) { + deps.pathToNames[path] = {}; + } + deps.pathToNames[path][provide] = true; + } + for (var j = 0; require = requires[j]; j++) { + if (!(path in deps.requires)) { + deps.requires[path] = {}; + } + deps.requires[path][require] = true; + } + } +}; + + + + +// NOTE(nnaze): The debug DOM loader was included in base.js as an original way +// to do "debug-mode" development. The dependency system can sometimes be +// confusing, as can the debug DOM loader's asynchronous nature. +// +// With the DOM loader, a call to goog.require() is not blocking -- the script +// will not load until some point after the current script. If a namespace is +// needed at runtime, it needs to be defined in a previous script, or loaded via +// require() with its registered dependencies. +// User-defined namespaces may need their own deps file. See http://go/js_deps, +// http://go/genjsdeps, or, externally, DepsWriter. +// https://developers.google.com/closure/library/docs/depswriter +// +// Because of legacy clients, the DOM loader can't be easily removed from +// base.js. Work is being done to make it disableable or replaceable for +// different environments (DOM-less JavaScript interpreters like Rhino or V8, +// for example). See bootstrap/ for more information. + + +/** + * @define {boolean} Whether to enable the debug loader. + * + * If enabled, a call to goog.require() will attempt to load the namespace by + * appending a script tag to the DOM (if the namespace has been registered). + * + * If disabled, goog.require() will simply assert that the namespace has been + * provided (and depend on the fact that some outside tool correctly ordered + * the script). + */ +goog.define('goog.ENABLE_DEBUG_LOADER', true); + + +/** + * Implements a system for the dynamic resolution of dependencies that works in + * parallel with the BUILD system. Note that all calls to goog.require will be + * stripped by the JSCompiler when the --closure_pass option is used. + * @see goog.provide + * @param {string} name Namespace to include (as was given in goog.provide()) in + * the form "goog.package.part". + */ +goog.require = function(name) { + + // If the object already exists we do not need do do anything. + // TODO(arv): If we start to support require based on file name this has to + // change. + // TODO(arv): If we allow goog.foo.* this has to change. + // TODO(arv): If we implement dynamic load after page load we should probably + // not remove this code for the compiled output. + if (!COMPILED) { + if (goog.isProvided_(name)) { + return; + } + + if (goog.ENABLE_DEBUG_LOADER) { + var path = goog.getPathFromDeps_(name); + if (path) { + goog.included_[path] = true; + goog.writeScripts_(); + return; + } + } + + var errorMessage = 'goog.require could not find: ' + name; + if (goog.global.console) { + goog.global.console['error'](errorMessage); + } + + + throw Error(errorMessage); + + } +}; + + +/** + * Path for included scripts. + * @type {string} + */ +goog.basePath = ''; + + +/** + * A hook for overriding the base path. + * @type {string|undefined} + */ +goog.global.CLOSURE_BASE_PATH; + + +/** + * Whether to write out Closure's deps file. By default, the deps are written. + * @type {boolean|undefined} + */ +goog.global.CLOSURE_NO_DEPS; + + +/** + * A function to import a single script. This is meant to be overridden when + * Closure is being run in non-HTML contexts, such as web workers. It's defined + * in the global scope so that it can be set before base.js is loaded, which + * allows deps.js to be imported properly. + * + * The function is passed the script source, which is a relative URI. It should + * return true if the script was imported, false otherwise. + * @type {(function(string): boolean)|undefined} + */ +goog.global.CLOSURE_IMPORT_SCRIPT; + + +/** + * Null function used for default values of callbacks, etc. + * @return {void} Nothing. + */ +goog.nullFunction = function() {}; + + +/** + * The identity function. Returns its first argument. + * + * @param {*=} opt_returnValue The single value that will be returned. + * @param {...*} var_args Optional trailing arguments. These are ignored. + * @return {?} The first argument. We can't know the type -- just pass it along + * without type. + * @deprecated Use goog.functions.identity instead. + */ +goog.identityFunction = function(opt_returnValue, var_args) { + return opt_returnValue; +}; + + +/** + * When defining a class Foo with an abstract method bar(), you can do: + * Foo.prototype.bar = goog.abstractMethod + * + * Now if a subclass of Foo fails to override bar(), an error will be thrown + * when bar() is invoked. + * + * Note: This does not take the name of the function to override as an argument + * because that would make it more difficult to obfuscate our JavaScript code. + * + * @type {!Function} + * @throws {Error} when invoked to indicate the method should be overridden. + */ +goog.abstractMethod = function() { + throw Error('unimplemented abstract method'); +}; + + +/** + * Adds a {@code getInstance} static method that always returns the same + * instance object. + * @param {!Function} ctor The constructor for the class to add the static + * method to. + */ +goog.addSingletonGetter = function(ctor) { + ctor.getInstance = function() { + if (ctor.instance_) { + return ctor.instance_; + } + if (goog.DEBUG) { + // NOTE: JSCompiler can't optimize away Array#push. + goog.instantiatedSingletons_[goog.instantiatedSingletons_.length] = ctor; + } + return ctor.instance_ = new ctor; + }; +}; + + +/** + * All singleton classes that have been instantiated, for testing. Don't read + * it directly, use the {@code goog.testing.singleton} module. The compiler + * removes this variable if unused. + * @type {!Array.<!Function>} + * @private + */ +goog.instantiatedSingletons_ = []; + + +/** + * True if goog.dependencies_ is available. + * @const {boolean} + */ +goog.DEPENDENCIES_ENABLED = !COMPILED && goog.ENABLE_DEBUG_LOADER; + + +if (goog.DEPENDENCIES_ENABLED) { + /** + * Object used to keep track of urls that have already been added. This record + * allows the prevention of circular dependencies. + * @type {Object} + * @private + */ + goog.included_ = {}; + + + /** + * This object is used to keep track of dependencies and other data that is + * used for loading scripts. + * @private + * @type {Object} + */ + goog.dependencies_ = { + pathToNames: {}, // 1 to many + nameToPath: {}, // 1 to 1 + requires: {}, // 1 to many + // Used when resolving dependencies to prevent us from visiting file twice. + visited: {}, + written: {} // Used to keep track of script files we have written. + }; + + + /** + * Tries to detect whether is in the context of an HTML document. + * @return {boolean} True if it looks like HTML document. + * @private + */ + goog.inHtmlDocument_ = function() { + var doc = goog.global.document; + return typeof doc != 'undefined' && + 'write' in doc; // XULDocument misses write. + }; + + + /** + * Tries to detect the base path of base.js script that bootstraps Closure. + * @private + */ + goog.findBasePath_ = function() { + if (goog.global.CLOSURE_BASE_PATH) { + goog.basePath = goog.global.CLOSURE_BASE_PATH; + return; + } else if (!goog.inHtmlDocument_()) { + return; + } + var doc = goog.global.document; + var scripts = doc.getElementsByTagName('script'); + // Search backwards since the current script is in almost all cases the one + // that has base.js. + for (var i = scripts.length - 1; i >= 0; --i) { + var src = scripts[i].src; + var qmark = src.lastIndexOf('?'); + var l = qmark == -1 ? src.length : qmark; + if (src.substr(l - 7, 7) == 'base.js') { + goog.basePath = src.substr(0, l - 7); + return; + } + } + }; + + + /** + * Imports a script if, and only if, that script hasn't already been imported. + * (Must be called at execution time) + * @param {string} src Script source. + * @private + */ + goog.importScript_ = function(src) { + var importScript = goog.global.CLOSURE_IMPORT_SCRIPT || + goog.writeScriptTag_; + if (!goog.dependencies_.written[src] && importScript(src)) { + goog.dependencies_.written[src] = true; + } + }; + + + /** + * The default implementation of the import function. Writes a script tag to + * import the script. + * + * @param {string} src The script source. + * @return {boolean} True if the script was imported, false otherwise. + * @private + */ + goog.writeScriptTag_ = function(src) { + if (goog.inHtmlDocument_()) { + var doc = goog.global.document; + + // If the user tries to require a new symbol after document load, + // something has gone terribly wrong. Doing a document.write would + // wipe out the page. + if (doc.readyState == 'complete') { + // Certain test frameworks load base.js multiple times, which tries + // to write deps.js each time. If that happens, just fail silently. + // These frameworks wipe the page between each load of base.js, so this + // is OK. + var isDeps = /\bdeps.js$/.test(src); + if (isDeps) { + return false; + } else { + throw Error('Cannot write "' + src + '" after document load'); + } + } + + doc.write( + '<script type="text/javascript" src="' + src + '"></' + 'script>'); + return true; + } else { + return false; + } + }; + + + /** + * Resolves dependencies based on the dependencies added using addDependency + * and calls importScript_ in the correct order. + * @private + */ + goog.writeScripts_ = function() { + // The scripts we need to write this time. + var scripts = []; + var seenScript = {}; + var deps = goog.dependencies_; + + function visitNode(path) { + if (path in deps.written) { + return; + } + + // We have already visited this one. We can get here if we have cyclic + // dependencies. + if (path in deps.visited) { + if (!(path in seenScript)) { + seenScript[path] = true; + scripts.push(path); + } + return; + } + + deps.visited[path] = true; + + if (path in deps.requires) { + for (var requireName in deps.requires[path]) { + // If the required name is defined, we assume that it was already + // bootstrapped by other means. + if (!goog.isProvided_(requireName)) { + if (requireName in deps.nameToPath) { + visitNode(deps.nameToPath[requireName]); + } else { + throw Error('Undefined nameToPath for ' + requireName); + } + } + } + } + + if (!(path in seenScript)) { + seenScript[path] = true; + scripts.push(path); + } + } + + for (var path in goog.included_) { + if (!deps.written[path]) { + visitNode(path); + } + } + + for (var i = 0; i < scripts.length; i++) { + if (scripts[i]) { + goog.importScript_(goog.basePath + scripts[i]); + } else { + throw Error('Undefined script input'); + } + } + }; + + + /** + * Looks at the dependency rules and tries to determine the script file that + * fulfills a particular rule. + * @param {string} rule In the form goog.namespace.Class or project.script. + * @return {?string} Url corresponding to the rule, or null. + * @private + */ + goog.getPathFromDeps_ = function(rule) { + if (rule in goog.dependencies_.nameToPath) { + return goog.dependencies_.nameToPath[rule]; + } else { + return null; + } + }; + + goog.findBasePath_(); + + // Allow projects to manage the deps files themselves. + if (!goog.global.CLOSURE_NO_DEPS) { + goog.importScript_(goog.basePath + 'deps.js'); + } +} + + + +//============================================================================== +// Language Enhancements +//============================================================================== + + +/** + * This is a "fixed" version of the typeof operator. It differs from the typeof + * operator in such a way that null returns 'null' and arrays return 'array'. + * @param {*} value The value to get the type of. + * @return {string} The name of the type. + */ +goog.typeOf = function(value) { + var s = typeof value; + if (s == 'object') { + if (value) { + // Check these first, so we can avoid calling Object.prototype.toString if + // possible. + // + // IE improperly marshals tyepof across execution contexts, but a + // cross-context object will still return false for "instanceof Object". + if (value instanceof Array) { + return 'array'; + } else if (value instanceof Object) { + return s; + } + + // HACK: In order to use an Object prototype method on the arbitrary + // value, the compiler requires the value be cast to type Object, + // even though the ECMA spec explicitly allows it. + var className = Object.prototype.toString.call( + /** @type {Object} */ (value)); + // In Firefox 3.6, attempting to access iframe window objects' length + // property throws an NS_ERROR_FAILURE, so we need to special-case it + // here. + if (className == '[object Window]') { + return 'object'; + } + + // We cannot always use constructor == Array or instanceof Array because + // different frames have different Array objects. In IE6, if the iframe + // where the array was created is destroyed, the array loses its + // prototype. Then dereferencing val.splice here throws an exception, so + // we can't use goog.isFunction. Calling typeof directly returns 'unknown' + // so that will work. In this case, this function will return false and + // most array functions will still work because the array is still + // array-like (supports length and []) even though it has lost its + // prototype. + // Mark Miller noticed that Object.prototype.toString + // allows access to the unforgeable [[Class]] property. + // 15.2.4.2 Object.prototype.toString ( ) + // When the toString method is called, the following steps are taken: + // 1. Get the [[Class]] property of this object. + // 2. Compute a string value by concatenating the three strings + // "[object ", Result(1), and "]". + // 3. Return Result(2). + // and this behavior survives the destruction of the execution context. + if ((className == '[object Array]' || + // In IE all non value types are wrapped as objects across window + // boundaries (not iframe though) so we have to do object detection + // for this edge case. + typeof value.length == 'number' && + typeof value.splice != 'undefined' && + typeof value.propertyIsEnumerable != 'undefined' && + !value.propertyIsEnumerable('splice') + + )) { + return 'array'; + } + // HACK: There is still an array case that fails. + // function ArrayImpostor() {} + // ArrayImpostor.prototype = []; + // var impostor = new ArrayImpostor; + // this can be fixed by getting rid of the fast path + // (value instanceof Array) and solely relying on + // (value && Object.prototype.toString.vall(value) === '[object Array]') + // but that would require many more function calls and is not warranted + // unless closure code is receiving objects from untrusted sources. + + // IE in cross-window calls does not correctly marshal the function type + // (it appears just as an object) so we cannot use just typeof val == + // 'function'. However, if the object has a call property, it is a + // function. + if ((className == '[object Function]' || + typeof value.call != 'undefined' && + typeof value.propertyIsEnumerable != 'undefined' && + !value.propertyIsEnumerable('call'))) { + return 'function'; + } + + } else { + return 'null'; + } + + } else if (s == 'function' && typeof value.call == 'undefined') { + // In Safari typeof nodeList returns 'function', and on Firefox typeof + // behaves similarly for HTML{Applet,Embed,Object}, Elements and RegExps. We + // would like to return object for those and we can detect an invalid + // function by making sure that the function object has a call method. + return 'object'; + } + return s; +}; + + +/** + * Returns true if the specified value is null. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is null. + */ +goog.isNull = function(val) { + return val === null; +}; + + +/** + * Returns true if the specified value is defined and not null. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is defined and not null. + */ +goog.isDefAndNotNull = function(val) { + // Note that undefined == null. + return val != null; +}; + + +/** + * Returns true if the specified value is an array. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is an array. + */ +goog.isArray = function(val) { + return goog.typeOf(val) == 'array'; +}; + + +/** + * Returns true if the object looks like an array. To qualify as array like + * the value needs to be either a NodeList or an object with a Number length + * property. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is an array. + */ +goog.isArrayLike = function(val) { + var type = goog.typeOf(val); + return type == 'array' || type == 'object' && typeof val.length == 'number'; +}; + + +/** + * Returns true if the object looks like a Date. To qualify as Date-like the + * value needs to be an object and have a getFullYear() function. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is a like a Date. + */ +goog.isDateLike = function(val) { + return goog.isObject(val) && typeof val.getFullYear == 'function'; +}; + + +/** + * Returns true if the specified value is a string. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is a string. + */ +goog.isString = function(val) { + return typeof val == 'string'; +}; + + +/** + * Returns true if the specified value is a boolean. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is boolean. + */ +goog.isBoolean = function(val) { + return typeof val == 'boolean'; +}; + + +/** + * Returns true if the specified value is a number. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is a number. + */ +goog.isNumber = function(val) { + return typeof val == 'number'; +}; + + +/** + * Returns true if the specified value is a function. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is a function. + */ +goog.isFunction = function(val) { + return goog.typeOf(val) == 'function'; +}; + + +/** + * Returns true if the specified value is an object. This includes arrays and + * functions. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is an object. + */ +goog.isObject = function(val) { + var type = typeof val; + return type == 'object' && val != null || type == 'function'; + // return Object(val) === val also works, but is slower, especially if val is + // not an object. +}; + + +/** + * Gets a unique ID for an object. This mutates the object so that further calls + * with the same object as a parameter returns the same value. The unique ID is + * guaranteed to be unique across the current session amongst objects that are + * passed into {@code getUid}. There is no guarantee that the ID is unique or + * consistent across sessions. It is unsafe to generate unique ID for function + * prototypes. + * + * @param {Object} obj The object to get the unique ID for. + * @return {number} The unique ID for the object. + */ +goog.getUid = function(obj) { + // TODO(arv): Make the type stricter, do not accept null. + + // In Opera window.hasOwnProperty exists but always returns false so we avoid + // using it. As a consequence the unique ID generated for BaseClass.prototype + // and SubClass.prototype will be the same. + return obj[goog.UID_PROPERTY_] || + (obj[goog.UID_PROPERTY_] = ++goog.uidCounter_); +}; + + +/** + * Whether the given object is alreay assigned a unique ID. + * + * This does not modify the object. + * + * @param {Object} obj The object to check. + * @return {boolean} Whether there an assigned unique id for the object. + */ +goog.hasUid = function(obj) { + return !!obj[goog.UID_PROPERTY_]; +}; + + +/** + * Removes the unique ID from an object. This is useful if the object was + * previously mutated using {@code goog.getUid} in which case the mutation is + * undone. + * @param {Object} obj The object to remove the unique ID field from. + */ +goog.removeUid = function(obj) { + // TODO(arv): Make the type stricter, do not accept null. + + // In IE, DOM nodes are not instances of Object and throw an exception if we + // try to delete. Instead we try to use removeAttribute. + if ('removeAttribute' in obj) { + obj.removeAttribute(goog.UID_PROPERTY_); + } + /** @preserveTry */ + try { + delete obj[goog.UID_PROPERTY_]; + } catch (ex) { + } +}; + + +/** + * Name for unique ID property. Initialized in a way to help avoid collisions + * with other closure JavaScript on the same page. + * @type {string} + * @private + */ +goog.UID_PROPERTY_ = 'closure_uid_' + ((Math.random() * 1e9) >>> 0); + + +/** + * Counter for UID. + * @type {number} + * @private + */ +goog.uidCounter_ = 0; + + +/** + * Adds a hash code field to an object. The hash code is unique for the + * given object. + * @param {Object} obj The object to get the hash code for. + * @return {number} The hash code for the object. + * @deprecated Use goog.getUid instead. + */ +goog.getHashCode = goog.getUid; + + +/** + * Removes the hash code field from an object. + * @param {Object} obj The object to remove the field from. + * @deprecated Use goog.removeUid instead. + */ +goog.removeHashCode = goog.removeUid; + + +/** + * Clones a value. The input may be an Object, Array, or basic type. Objects and + * arrays will be cloned recursively. + * + * WARNINGS: + * <code>goog.cloneObject</code> does not detect reference loops. Objects that + * refer to themselves will cause infinite recursion. + * + * <code>goog.cloneObject</code> is unaware of unique identifiers, and copies + * UIDs created by <code>getUid</code> into cloned results. + * + * @param {*} obj The value to clone. + * @return {*} A clone of the input value. + * @deprecated goog.cloneObject is unsafe. Prefer the goog.object methods. + */ +goog.cloneObject = function(obj) { + var type = goog.typeOf(obj); + if (type == 'object' || type == 'array') { + if (obj.clone) { + return obj.clone(); + } + var clone = type == 'array' ? [] : {}; + for (var key in obj) { + clone[key] = goog.cloneObject(obj[key]); + } + return clone; + } + + return obj; +}; + + +/** + * A native implementation of goog.bind. + * @param {Function} fn A function to partially apply. + * @param {Object|undefined} selfObj Specifies the object which this should + * point to when the function is run. + * @param {...*} var_args Additional arguments that are partially applied to the + * function. + * @return {!Function} A partially-applied form of the function bind() was + * invoked as a method of. + * @private + * @suppress {deprecated} The compiler thinks that Function.prototype.bind is + * deprecated because some people have declared a pure-JS version. + * Only the pure-JS version is truly deprecated. + */ +goog.bindNative_ = function(fn, selfObj, var_args) { + return /** @type {!Function} */ (fn.call.apply(fn.bind, arguments)); +}; + + +/** + * A pure-JS implementation of goog.bind. + * @param {Function} fn A function to partially apply. + * @param {Object|undefined} selfObj Specifies the object which this should + * point to when the function is run. + * @param {...*} var_args Additional arguments that are partially applied to the + * function. + * @return {!Function} A partially-applied form of the function bind() was + * invoked as a method of. + * @private + */ +goog.bindJs_ = function(fn, selfObj, var_args) { + if (!fn) { + throw new Error(); + } + + if (arguments.length > 2) { + var boundArgs = Array.prototype.slice.call(arguments, 2); + return function() { + // Prepend the bound arguments to the current arguments. + var newArgs = Array.prototype.slice.call(arguments); + Array.prototype.unshift.apply(newArgs, boundArgs); + return fn.apply(selfObj, newArgs); + }; + + } else { + return function() { + return fn.apply(selfObj, arguments); + }; + } +}; + + +/** + * Partially applies this function to a particular 'this object' and zero or + * more arguments. The result is a new function with some arguments of the first + * function pre-filled and the value of this 'pre-specified'. + * + * Remaining arguments specified at call-time are appended to the pre-specified + * ones. + * + * Also see: {@link #partial}. + * + * Usage: + * <pre>var barMethBound = bind(myFunction, myObj, 'arg1', 'arg2'); + * barMethBound('arg3', 'arg4');</pre> + * + * @param {?function(this:T, ...)} fn A function to partially apply. + * @param {T} selfObj Specifies the object which this should point to when the + * function is run. + * @param {...*} var_args Additional arguments that are partially applied to the + * function. + * @return {!Function} A partially-applied form of the function bind() was + * invoked as a method of. + * @template T + * @suppress {deprecated} See above. + */ +goog.bind = function(fn, selfObj, var_args) { + // TODO(nicksantos): narrow the type signature. + if (Function.prototype.bind && + // NOTE(nicksantos): Somebody pulled base.js into the default Chrome + // extension environment. This means that for Chrome extensions, they get + // the implementation of Function.prototype.bind that calls goog.bind + // instead of the native one. Even worse, we don't want to introduce a + // circular dependency between goog.bind and Function.prototype.bind, so + // we have to hack this to make sure it works correctly. + Function.prototype.bind.toString().indexOf('native code') != -1) { + goog.bind = goog.bindNative_; + } else { + goog.bind = goog.bindJs_; + } + return goog.bind.apply(null, arguments); +}; + + +/** + * Like bind(), except that a 'this object' is not required. Useful when the + * target function is already bound. + * + * Usage: + * var g = partial(f, arg1, arg2); + * g(arg3, arg4); + * + * @param {Function} fn A function to partially apply. + * @param {...*} var_args Additional arguments that are partially applied to fn. + * @return {!Function} A partially-applied form of the function bind() was + * invoked as a method of. + */ +goog.partial = function(fn, var_args) { + var args = Array.prototype.slice.call(arguments, 1); + return function() { + // Clone the array (with slice()) and append additional arguments + // to the existing arguments. + var newArgs = args.slice(); + newArgs.push.apply(newArgs, arguments); + return fn.apply(this, newArgs); + }; +}; + + +/** + * Copies all the members of a source object to a target object. This method + * does not work on all browsers for all objects that contain keys such as + * toString or hasOwnProperty. Use goog.object.extend for this purpose. + * @param {Object} target Target. + * @param {Object} source Source. + */ +goog.mixin = function(target, source) { + for (var x in source) { + target[x] = source[x]; + } + + // For IE7 or lower, the for-in-loop does not contain any properties that are + // not enumerable on the prototype object (for example, isPrototypeOf from + // Object.prototype) but also it will not include 'replace' on objects that + // extend String and change 'replace' (not that it is common for anyone to + // extend anything except Object). +}; + + +/** + * @return {number} An integer value representing the number of milliseconds + * between midnight, January 1, 1970 and the current time. + */ +goog.now = (goog.TRUSTED_SITE && Date.now) || (function() { + // Unary plus operator converts its operand to a number which in the case of + // a date is done by calling getTime(). + return +new Date(); +}); + + +/** + * Evals JavaScript in the global scope. In IE this uses execScript, other + * browsers use goog.global.eval. If goog.global.eval does not evaluate in the + * global scope (for example, in Safari), appends a script tag instead. + * Throws an exception if neither execScript or eval is defined. + * @param {string} script JavaScript string. + */ +goog.globalEval = function(script) { + if (goog.global.execScript) { + goog.global.execScript(script, 'JavaScript'); + } else if (goog.global.eval) { + // Test to see if eval works + if (goog.evalWorksForGlobals_ == null) { + goog.global.eval('var _et_ = 1;'); + if (typeof goog.global['_et_'] != 'undefined') { + delete goog.global['_et_']; + goog.evalWorksForGlobals_ = true; + } else { + goog.evalWorksForGlobals_ = false; + } + } + + if (goog.evalWorksForGlobals_) { + goog.global.eval(script); + } else { + var doc = goog.global.document; + var scriptElt = doc.createElement('script'); + scriptElt.type = 'text/javascript'; + scriptElt.defer = false; + // Note(user): can't use .innerHTML since "t('<test>')" will fail and + // .text doesn't work in Safari 2. Therefore we append a text node. + scriptElt.appendChild(doc.createTextNode(script)); + doc.body.appendChild(scriptElt); + doc.body.removeChild(scriptElt); + } + } else { + throw Error('goog.globalEval not available'); + } +}; + + +/** + * Indicates whether or not we can call 'eval' directly to eval code in the + * global scope. Set to a Boolean by the first call to goog.globalEval (which + * empirically tests whether eval works for globals). @see goog.globalEval + * @type {?boolean} + * @private + */ +goog.evalWorksForGlobals_ = null; + + +/** + * Optional map of CSS class names to obfuscated names used with + * goog.getCssName(). + * @type {Object|undefined} + * @private + * @see goog.setCssNameMapping + */ +goog.cssNameMapping_; + + +/** + * Optional obfuscation style for CSS class names. Should be set to either + * 'BY_WHOLE' or 'BY_PART' if defined. + * @type {string|undefined} + * @private + * @see goog.setCssNameMapping + */ +goog.cssNameMappingStyle_; + + +/** + * Handles strings that are intended to be used as CSS class names. + * + * This function works in tandem with @see goog.setCssNameMapping. + * + * Without any mapping set, the arguments are simple joined with a hyphen and + * passed through unaltered. + * + * When there is a mapping, there are two possible styles in which these + * mappings are used. In the BY_PART style, each part (i.e. in between hyphens) + * of the passed in css name is rewritten according to the map. In the BY_WHOLE + * style, the full css name is looked up in the map directly. If a rewrite is + * not specified by the map, the compiler will output a warning. + * + * When the mapping is passed to the compiler, it will replace calls to + * goog.getCssName with the strings from the mapping, e.g. + * var x = goog.getCssName('foo'); + * var y = goog.getCssName(this.baseClass, 'active'); + * becomes: + * var x= 'foo'; + * var y = this.baseClass + '-active'; + * + * If one argument is passed it will be processed, if two are passed only the + * modifier will be processed, as it is assumed the first argument was generated + * as a result of calling goog.getCssName. + * + * @param {string} className The class name. + * @param {string=} opt_modifier A modifier to be appended to the class name. + * @return {string} The class name or the concatenation of the class name and + * the modifier. + */ +goog.getCssName = function(className, opt_modifier) { + var getMapping = function(cssName) { + return goog.cssNameMapping_[cssName] || cssName; + }; + + var renameByParts = function(cssName) { + // Remap all the parts individually. + var parts = cssName.split('-'); + var mapped = []; + for (var i = 0; i < parts.length; i++) { + mapped.push(getMapping(parts[i])); + } + return mapped.join('-'); + }; + + var rename; + if (goog.cssNameMapping_) { + rename = goog.cssNameMappingStyle_ == 'BY_WHOLE' ? + getMapping : renameByParts; + } else { + rename = function(a) { + return a; + }; + } + + if (opt_modifier) { + return className + '-' + rename(opt_modifier); + } else { + return rename(className); + } +}; + + +/** + * Sets the map to check when returning a value from goog.getCssName(). Example: + * <pre> + * goog.setCssNameMapping({ + * "goog": "a", + * "disabled": "b", + * }); + * + * var x = goog.getCssName('goog'); + * // The following evaluates to: "a a-b". + * goog.getCssName('goog') + ' ' + goog.getCssName(x, 'disabled') + * </pre> + * When declared as a map of string literals to string literals, the JSCompiler + * will replace all calls to goog.getCssName() using the supplied map if the + * --closure_pass flag is set. + * + * @param {!Object} mapping A map of strings to strings where keys are possible + * arguments to goog.getCssName() and values are the corresponding values + * that should be returned. + * @param {string=} opt_style The style of css name mapping. There are two valid + * options: 'BY_PART', and 'BY_WHOLE'. + * @see goog.getCssName for a description. + */ +goog.setCssNameMapping = function(mapping, opt_style) { + goog.cssNameMapping_ = mapping; + goog.cssNameMappingStyle_ = opt_style; +}; + + +/** + * To use CSS renaming in compiled mode, one of the input files should have a + * call to goog.setCssNameMapping() with an object literal that the JSCompiler + * can extract and use to replace all calls to goog.getCssName(). In uncompiled + * mode, JavaScript code should be loaded before this base.js file that declares + * a global variable, CLOSURE_CSS_NAME_MAPPING, which is used below. This is + * to ensure that the mapping is loaded before any calls to goog.getCssName() + * are made in uncompiled mode. + * + * A hook for overriding the CSS name mapping. + * @type {Object|undefined} + */ +goog.global.CLOSURE_CSS_NAME_MAPPING; + + +if (!COMPILED && goog.global.CLOSURE_CSS_NAME_MAPPING) { + // This does not call goog.setCssNameMapping() because the JSCompiler + // requires that goog.setCssNameMapping() be called with an object literal. + goog.cssNameMapping_ = goog.global.CLOSURE_CSS_NAME_MAPPING; +} + + +/** + * Gets a localized message. + * + * This function is a compiler primitive. If you give the compiler a localized + * message bundle, it will replace the string at compile-time with a localized + * version, and expand goog.getMsg call to a concatenated string. + * + * Messages must be initialized in the form: + * <code> + * var MSG_NAME = goog.getMsg('Hello {$placeholder}', {'placeholder': 'world'}); + * </code> + * + * @param {string} str Translatable string, places holders in the form {$foo}. + * @param {Object=} opt_values Map of place holder name to value. + * @return {string} message with placeholders filled. + */ +goog.getMsg = function(str, opt_values) { + if (opt_values) { + str = str.replace(/\{\$([^}]+)}/g, function(match, key) { + return key in opt_values ? opt_values[key] : match; + }); + } + return str; +}; + + +/** + * Gets a localized message. If the message does not have a translation, gives a + * fallback message. + * + * This is useful when introducing a new message that has not yet been + * translated into all languages. + * + * This function is a compiler primitive. Must be used in the form: + * <code>var x = goog.getMsgWithFallback(MSG_A, MSG_B);</code> + * where MSG_A and MSG_B were initialized with goog.getMsg. + * + * @param {string} a The preferred message. + * @param {string} b The fallback message. + * @return {string} The best translated message. + */ +goog.getMsgWithFallback = function(a, b) { + return a; +}; + + +/** + * Exposes an unobfuscated global namespace path for the given object. + * Note that fields of the exported object *will* be obfuscated, unless they are + * exported in turn via this function or goog.exportProperty. + * + * Also handy for making public items that are defined in anonymous closures. + * + * ex. goog.exportSymbol('public.path.Foo', Foo); + * + * ex. goog.exportSymbol('public.path.Foo.staticFunction', Foo.staticFunction); + * public.path.Foo.staticFunction(); + * + * ex. goog.exportSymbol('public.path.Foo.prototype.myMethod', + * Foo.prototype.myMethod); + * new public.path.Foo().myMethod(); + * + * @param {string} publicPath Unobfuscated name to export. + * @param {*} object Object the name should point to. + * @param {Object=} opt_objectToExportTo The object to add the path to; default + * is goog.global. + */ +goog.exportSymbol = function(publicPath, object, opt_objectToExportTo) { + goog.exportPath_(publicPath, object, opt_objectToExportTo); +}; + + +/** + * Exports a property unobfuscated into the object's namespace. + * ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction); + * ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod); + * @param {Object} object Object whose static property is being exported. + * @param {string} publicName Unobfuscated name to export. + * @param {*} symbol Object the name should point to. + */ +goog.exportProperty = function(object, publicName, symbol) { + object[publicName] = symbol; +}; + + +/** + * Inherit the prototype methods from one constructor into another. + * + * Usage: + * <pre> + * function ParentClass(a, b) { } + * ParentClass.prototype.foo = function(a) { }; + * + * function ChildClass(a, b, c) { + * ChildClass.base(this, 'constructor', a, b); + * } + * goog.inherits(ChildClass, ParentClass); + * + * var child = new ChildClass('a', 'b', 'see'); + * child.foo(); // This works. + * </pre> + * + * @param {Function} childCtor Child class. + * @param {Function} parentCtor Parent class. + */ +goog.inherits = function(childCtor, parentCtor) { + /** @constructor */ + function tempCtor() {}; + tempCtor.prototype = parentCtor.prototype; + childCtor.superClass_ = parentCtor.prototype; + childCtor.prototype = new tempCtor(); + /** @override */ + childCtor.prototype.constructor = childCtor; + + /** + * Calls superclass constructor/method. + * + * This function is only available if you use goog.inherits to + * express inheritance relationships between classes. + * + * NOTE: This is a replacement for goog.base and for superClass_ + * property defined in childCtor. + * + * @param {!Object} me Should always be "this". + * @param {string} methodName The method name to call. Calling + * superclass constructor can be done with the special string + * 'constructor'. + * @param {...*} var_args The arguments to pass to superclass + * method/constructor. + * @return {*} The return value of the superclass method/constructor. + */ + childCtor.base = function(me, methodName, var_args) { + var args = Array.prototype.slice.call(arguments, 2); + return parentCtor.prototype[methodName].apply(me, args); + }; +}; + + +/** + * Call up to the superclass. + * + * If this is called from a constructor, then this calls the superclass + * constructor with arguments 1-N. + * + * If this is called from a prototype method, then you must pass the name of the + * method as the second argument to this function. If you do not, you will get a + * runtime error. This calls the superclass' method with arguments 2-N. + * + * This function only works if you use goog.inherits to express inheritance + * relationships between your classes. + * + * This function is a compiler primitive. At compile-time, the compiler will do + * macro expansion to remove a lot of the extra overhead that this function + * introduces. The compiler will also enforce a lot of the assumptions that this + * function makes, and treat it as a compiler error if you break them. + * + * @param {!Object} me Should always be "this". + * @param {*=} opt_methodName The method name if calling a super method. + * @param {...*} var_args The rest of the arguments. + * @return {*} The return value of the superclass method. + * @suppress {es5Strict} This method can not be used in strict mode, but + * all Closure Library consumers must depend on this file. + */ +goog.base = function(me, opt_methodName, var_args) { + var caller = arguments.callee.caller; + + if (goog.STRICT_MODE_COMPATIBLE || (goog.DEBUG && !caller)) { + throw Error('arguments.caller not defined. goog.base() cannot be used ' + + 'with strict mode code. See ' + + 'http://www.ecma-international.org/ecma-262/5.1/#sec-C'); + } + + if (caller.superClass_) { + // This is a constructor. Call the superclass constructor. + return caller.superClass_.constructor.apply( + me, Array.prototype.slice.call(arguments, 1)); + } + + var args = Array.prototype.slice.call(arguments, 2); + var foundCaller = false; + for (var ctor = me.constructor; + ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) { + if (ctor.prototype[opt_methodName] === caller) { + foundCaller = true; + } else if (foundCaller) { + return ctor.prototype[opt_methodName].apply(me, args); + } + } + + // If we did not find the caller in the prototype chain, then one of two + // things happened: + // 1) The caller is an instance method. + // 2) This method was not called by the right caller. + if (me[opt_methodName] === caller) { + return me.constructor.prototype[opt_methodName].apply(me, args); + } else { + throw Error( + 'goog.base called from a method of one name ' + + 'to a method of a different name'); + } +}; + + +/** + * Allow for aliasing within scope functions. This function exists for + * uncompiled code - in compiled code the calls will be inlined and the aliases + * applied. In uncompiled code the function is simply run since the aliases as + * written are valid JavaScript. + * + * + * @param {function()} fn Function to call. This function can contain aliases + * to namespaces (e.g. "var dom = goog.dom") or classes + * (e.g. "var Timer = goog.Timer"). + */ +goog.scope = function(fn) { + fn.call(goog.global); +}; + + +/* + * To support uncompiled, strict mode bundles that use eval to divide source + * like so: + * eval('someSource;//# sourceUrl sourcefile.js'); + * We need to export the globally defined symbols "goog" and "COMPILED". + * Exporting "goog" breaks the compiler optimizations, so we required that + * be defined externally. + * NOTE: We don't use goog.exportSymbol here because we don't want to trigger + * extern generation when that compiler option is enabled. + */ +if (!COMPILED) { + goog.global['COMPILED'] = COMPILED; +} + + + +//============================================================================== +// goog.defineClass implementation +//============================================================================== + +/** + * Creates a restricted form of a Closure "class": + * - from the compiler's perspective, the instance returned from the + * constructor is sealed (no new properties may be added). This enables + * better checks. + * - the compiler will rewrite this definition to a form that is optimal + * for type checking and optimization (initially this will be a more + * traditional form). + * + * @param {Function} superClass The superclass, Object or null. + * @param {goog.defineClass.ClassDescriptor} def + * An object literal describing the + * the class. It may have the following properties: + * "constructor": the constructor function + * "statics": an object literal containing methods to add to the constructor + * as "static" methods or a function that will receive the constructor + * function as its only parameter to which static properties can + * be added. + * all other properties are added to the prototype. + * @return {!Function} The class constructor. + */ +goog.defineClass = function(superClass, def) { + // TODO(johnlenz): consider making the superClass an optional parameter. + var constructor = def.constructor; + var statics = def.statics; + // Wrap the constructor prior to setting up the prototype and static methods. + if (!constructor || constructor == Object.prototype.constructor) { + constructor = function() { + throw Error('cannot instantiate an interface (no constructor defined).'); + }; + } + + var cls = goog.defineClass.createSealingConstructor_(constructor, superClass); + if (superClass) { + goog.inherits(cls, superClass); + } + + // Remove all the properties that should not be copied to the prototype. + delete def.constructor; + delete def.statics; + + goog.defineClass.applyProperties_(cls.prototype, def); + if (statics != null) { + if (statics instanceof Function) { + statics(cls); + } else { + goog.defineClass.applyProperties_(cls, statics); + } + } + + return cls; +}; + + +/** + * @typedef { + * !Object| + * {constructor:!Function}| + * {constructor:!Function, statics:(Object|function(Function):void)}} + */ +goog.defineClass.ClassDescriptor; + + +/** + * @define {boolean} Whether the instances returned by + * goog.defineClass should be sealed when possible. + */ +goog.define('goog.defineClass.SEAL_CLASS_INSTANCES', goog.DEBUG); + + +/** + * If goog.defineClass.SEAL_CLASS_INSTANCES is enabled and Object.seal is + * defined, this function will wrap the constructor in a function that seals the + * results of the provided constructor function. + * + * @param {!Function} ctr The constructor whose results maybe be sealed. + * @param {Function} superClass The superclass constructor. + * @return {!Function} The replacement constructor. + * @private + */ +goog.defineClass.createSealingConstructor_ = function(ctr, superClass) { + if (goog.defineClass.SEAL_CLASS_INSTANCES && + Object.seal instanceof Function) { + // Don't seal subclasses of unsealable-tagged legacy classes. + if (superClass && superClass.prototype && + superClass.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_]) { + return ctr; + } + /** @this {*} */ + var wrappedCtr = function() { + // Don't seal an instance of a subclass when it calls the constructor of + // its super class as there is most likely still setup to do. + var instance = ctr.apply(this, arguments) || this; + if (this.constructor === wrappedCtr) { + Object.seal(instance); + } + return instance; + }; + return wrappedCtr; + } + return ctr; +}; + + +// TODO(johnlenz): share these values with the goog.object +/** + * The names of the fields that are defined on Object.prototype. + * @type {!Array.<string>} + * @private + * @const + */ +goog.defineClass.OBJECT_PROTOTYPE_FIELDS_ = [ + 'constructor', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'toLocaleString', + 'toString', + 'valueOf' +]; + + +// TODO(johnlenz): share this function with the goog.object +/** + * @param {!Object} target The object to add properties to. + * @param {!Object} source The object to copy properites from. + * @private + */ +goog.defineClass.applyProperties_ = function(target, source) { + // TODO(johnlenz): update this to support ES5 getters/setters + + var key; + for (key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + + // For IE the for-in-loop does not contain any properties that are not + // enumerable on the prototype object (for example isPrototypeOf from + // Object.prototype) and it will also not include 'replace' on objects that + // extend String and change 'replace' (not that it is common for anyone to + // extend anything except Object). + for (var i = 0; i < goog.defineClass.OBJECT_PROTOTYPE_FIELDS_.length; i++) { + key = goog.defineClass.OBJECT_PROTOTYPE_FIELDS_[i]; + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } +}; + + +/** + * Sealing classes breaks the older idiom of assigning properties on the + * prototype rather than in the constructor. As such, goog.defineClass + * must not seal subclasses of these old-style classes until they are fixed. + * Until then, this marks a class as "broken", instructing defineClass + * not to seal subclasses. + * @param {!Function} ctr The legacy constructor to tag as unsealable. + */ +goog.tagUnsealableClass = function(ctr) { + if (!COMPILED && goog.defineClass.SEAL_CLASS_INSTANCES) { + ctr.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_] = true; + } +}; + + +/** + * Name for unsealable tag property. + * @const @private {string} + */ +goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_ = 'goog_defineClass_legacy_unsealable'; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/debug.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/debug.js new file mode 100644 index 0000000..e6e28e1 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/debug.js @@ -0,0 +1,590 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Logging and debugging utilities. + * + * @see ../demos/debug.html + */ + +goog.provide('goog.debug'); + +goog.require('goog.array'); +goog.require('goog.string'); +goog.require('goog.structs.Set'); +goog.require('goog.userAgent'); + + +/** @define {boolean} Whether logging should be enabled. */ +goog.define('goog.debug.LOGGING_ENABLED', goog.DEBUG); + + +/** + * Catches onerror events fired by windows and similar objects. + * @param {function(Object)} logFunc The function to call with the error + * information. + * @param {boolean=} opt_cancel Whether to stop the error from reaching the + * browser. + * @param {Object=} opt_target Object that fires onerror events. + */ +goog.debug.catchErrors = function(logFunc, opt_cancel, opt_target) { + var target = opt_target || goog.global; + var oldErrorHandler = target.onerror; + var retVal = !!opt_cancel; + + // Chrome interprets onerror return value backwards (http://crbug.com/92062) + // until it was fixed in webkit revision r94061 (Webkit 535.3). This + // workaround still needs to be skipped in Safari after the webkit change + // gets pushed out in Safari. + // See https://bugs.webkit.org/show_bug.cgi?id=67119 + if (goog.userAgent.WEBKIT && + !goog.userAgent.isVersionOrHigher('535.3')) { + retVal = !retVal; + } + + /** + * New onerror handler for this target. This onerror handler follows the spec + * according to + * http://www.whatwg.org/specs/web-apps/current-work/#runtime-script-errors + * The spec was changed in August 2013 to support receiving column information + * and an error object for all scripts on the same origin or cross origin + * scripts with the proper headers. See + * https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror + * + * @param {string} message The error message. For cross-origin errors, this + * will be scrubbed to just "Script error.". For new browsers that have + * updated to follow the latest spec, errors that come from origins that + * have proper cross origin headers will not be scrubbed. + * @param {string} url The URL of the script that caused the error. The URL + * will be scrubbed to "" for cross origin scripts unless the script has + * proper cross origin headers and the browser has updated to the latest + * spec. + * @param {number} line The line number in the script that the error + * occurred on. + * @param {number=} opt_col The optional column number that the error + * occurred on. Only browsers that have updated to the latest spec will + * include this. + * @param {Error=} opt_error The optional actual error object for this + * error that should include the stack. Only browsers that have updated + * to the latest spec will inlude this parameter. + * @return {boolean} Whether to prevent the error from reaching the browser. + */ + target.onerror = function(message, url, line, opt_col, opt_error) { + if (oldErrorHandler) { + oldErrorHandler(message, url, line, opt_col, opt_error); + } + logFunc({ + message: message, + fileName: url, + line: line, + col: opt_col, + error: opt_error + }); + return retVal; + }; +}; + + +/** + * Creates a string representing an object and all its properties. + * @param {Object|null|undefined} obj Object to expose. + * @param {boolean=} opt_showFn Show the functions as well as the properties, + * default is false. + * @return {string} The string representation of {@code obj}. + */ +goog.debug.expose = function(obj, opt_showFn) { + if (typeof obj == 'undefined') { + return 'undefined'; + } + if (obj == null) { + return 'NULL'; + } + var str = []; + + for (var x in obj) { + if (!opt_showFn && goog.isFunction(obj[x])) { + continue; + } + var s = x + ' = '; + /** @preserveTry */ + try { + s += obj[x]; + } catch (e) { + s += '*** ' + e + ' ***'; + } + str.push(s); + } + return str.join('\n'); +}; + + +/** + * Creates a string representing a given primitive or object, and for an + * object, all its properties and nested objects. WARNING: If an object is + * given, it and all its nested objects will be modified. To detect reference + * cycles, this method identifies objects using goog.getUid() which mutates the + * object. + * @param {*} obj Object to expose. + * @param {boolean=} opt_showFn Also show properties that are functions (by + * default, functions are omitted). + * @return {string} A string representation of {@code obj}. + */ +goog.debug.deepExpose = function(obj, opt_showFn) { + var str = []; + + var helper = function(obj, space, parentSeen) { + var nestspace = space + ' '; + var seen = new goog.structs.Set(parentSeen); + + var indentMultiline = function(str) { + return str.replace(/\n/g, '\n' + space); + }; + + /** @preserveTry */ + try { + if (!goog.isDef(obj)) { + str.push('undefined'); + } else if (goog.isNull(obj)) { + str.push('NULL'); + } else if (goog.isString(obj)) { + str.push('"' + indentMultiline(obj) + '"'); + } else if (goog.isFunction(obj)) { + str.push(indentMultiline(String(obj))); + } else if (goog.isObject(obj)) { + if (seen.contains(obj)) { + str.push('*** reference loop detected ***'); + } else { + seen.add(obj); + str.push('{'); + for (var x in obj) { + if (!opt_showFn && goog.isFunction(obj[x])) { + continue; + } + str.push('\n'); + str.push(nestspace); + str.push(x + ' = '); + helper(obj[x], nestspace, seen); + } + str.push('\n' + space + '}'); + } + } else { + str.push(obj); + } + } catch (e) { + str.push('*** ' + e + ' ***'); + } + }; + + helper(obj, '', new goog.structs.Set()); + return str.join(''); +}; + + +/** + * Recursively outputs a nested array as a string. + * @param {Array} arr The array. + * @return {string} String representing nested array. + */ +goog.debug.exposeArray = function(arr) { + var str = []; + for (var i = 0; i < arr.length; i++) { + if (goog.isArray(arr[i])) { + str.push(goog.debug.exposeArray(arr[i])); + } else { + str.push(arr[i]); + } + } + return '[ ' + str.join(', ') + ' ]'; +}; + + +/** + * Exposes an exception that has been caught by a try...catch and outputs the + * error with a stack trace. + * @param {Object} err Error object or string. + * @param {Function=} opt_fn Optional function to start stack trace from. + * @return {string} Details of exception. + */ +goog.debug.exposeException = function(err, opt_fn) { + /** @preserveTry */ + try { + var e = goog.debug.normalizeErrorObject(err); + + // Create the error message + var error = 'Message: ' + goog.string.htmlEscape(e.message) + + '\nUrl: <a href="view-source:' + e.fileName + '" target="_new">' + + e.fileName + '</a>\nLine: ' + e.lineNumber + '\n\nBrowser stack:\n' + + goog.string.htmlEscape(e.stack + '-> ') + + '[end]\n\nJS stack traversal:\n' + goog.string.htmlEscape( + goog.debug.getStacktrace(opt_fn) + '-> '); + return error; + } catch (e2) { + return 'Exception trying to expose exception! You win, we lose. ' + e2; + } +}; + + +/** + * Normalizes the error/exception object between browsers. + * @param {Object} err Raw error object. + * @return {!Object} Normalized error object. + */ +goog.debug.normalizeErrorObject = function(err) { + var href = goog.getObjectByName('window.location.href'); + if (goog.isString(err)) { + return { + 'message': err, + 'name': 'Unknown error', + 'lineNumber': 'Not available', + 'fileName': href, + 'stack': 'Not available' + }; + } + + var lineNumber, fileName; + var threwError = false; + + try { + lineNumber = err.lineNumber || err.line || 'Not available'; + } catch (e) { + // Firefox 2 sometimes throws an error when accessing 'lineNumber': + // Message: Permission denied to get property UnnamedClass.lineNumber + lineNumber = 'Not available'; + threwError = true; + } + + try { + fileName = err.fileName || err.filename || err.sourceURL || + // $googDebugFname may be set before a call to eval to set the filename + // that the eval is supposed to present. + goog.global['$googDebugFname'] || href; + } catch (e) { + // Firefox 2 may also throw an error when accessing 'filename'. + fileName = 'Not available'; + threwError = true; + } + + // The IE Error object contains only the name and the message. + // The Safari Error object uses the line and sourceURL fields. + if (threwError || !err.lineNumber || !err.fileName || !err.stack || + !err.message || !err.name) { + return { + 'message': err.message || 'Not available', + 'name': err.name || 'UnknownError', + 'lineNumber': lineNumber, + 'fileName': fileName, + 'stack': err.stack || 'Not available' + }; + } + + // Standards error object + return err; +}; + + +/** + * Converts an object to an Error if it's a String, + * adds a stacktrace if there isn't one, + * and optionally adds an extra message. + * @param {Error|string} err the original thrown object or string. + * @param {string=} opt_message optional additional message to add to the + * error. + * @return {!Error} If err is a string, it is used to create a new Error, + * which is enhanced and returned. Otherwise err itself is enhanced + * and returned. + */ +goog.debug.enhanceError = function(err, opt_message) { + var error; + if (typeof err == 'string') { + error = Error(err); + if (Error.captureStackTrace) { + // Trim this function off the call stack, if we can. + Error.captureStackTrace(error, goog.debug.enhanceError); + } + } else { + error = err; + } + + if (!error.stack) { + error.stack = goog.debug.getStacktrace(goog.debug.enhanceError); + } + if (opt_message) { + // find the first unoccupied 'messageX' property + var x = 0; + while (error['message' + x]) { + ++x; + } + error['message' + x] = String(opt_message); + } + return error; +}; + + +/** + * Gets the current stack trace. Simple and iterative - doesn't worry about + * catching circular references or getting the args. + * @param {number=} opt_depth Optional maximum depth to trace back to. + * @return {string} A string with the function names of all functions in the + * stack, separated by \n. + * @suppress {es5Strict} + */ +goog.debug.getStacktraceSimple = function(opt_depth) { + if (goog.STRICT_MODE_COMPATIBLE) { + var stack = goog.debug.getNativeStackTrace_(goog.debug.getStacktraceSimple); + if (stack) { + return stack; + } + // NOTE: browsers that have strict mode support also have native "stack" + // properties. Fall-through for legacy browser support. + } + + var sb = []; + var fn = arguments.callee.caller; + var depth = 0; + + while (fn && (!opt_depth || depth < opt_depth)) { + sb.push(goog.debug.getFunctionName(fn)); + sb.push('()\n'); + /** @preserveTry */ + try { + fn = fn.caller; + } catch (e) { + sb.push('[exception trying to get caller]\n'); + break; + } + depth++; + if (depth >= goog.debug.MAX_STACK_DEPTH) { + sb.push('[...long stack...]'); + break; + } + } + if (opt_depth && depth >= opt_depth) { + sb.push('[...reached max depth limit...]'); + } else { + sb.push('[end]'); + } + + return sb.join(''); +}; + + +/** + * Max length of stack to try and output + * @type {number} + */ +goog.debug.MAX_STACK_DEPTH = 50; + + +/** + * @param {Function} fn The function to start getting the trace from. + * @return {?string} + * @private + */ +goog.debug.getNativeStackTrace_ = function(fn) { + var tempErr = new Error(); + if (Error.captureStackTrace) { + Error.captureStackTrace(tempErr, fn); + return String(tempErr.stack); + } else { + // IE10, only adds stack traces when an exception is thrown. + try { + throw tempErr; + } catch (e) { + tempErr = e; + } + var stack = tempErr.stack; + if (stack) { + return String(stack); + } + } + return null; +}; + + +/** + * Gets the current stack trace, either starting from the caller or starting + * from a specified function that's currently on the call stack. + * @param {Function=} opt_fn Optional function to start getting the trace from. + * If not provided, defaults to the function that called this. + * @return {string} Stack trace. + * @suppress {es5Strict} + */ +goog.debug.getStacktrace = function(opt_fn) { + var stack; + if (goog.STRICT_MODE_COMPATIBLE) { + // Try to get the stack trace from the environment if it is available. + var contextFn = opt_fn || goog.debug.getStacktrace; + stack = goog.debug.getNativeStackTrace_(contextFn); + } + if (!stack) { + // NOTE: browsers that have strict mode support also have native "stack" + // properties. This function will throw in strict mode. + stack = goog.debug.getStacktraceHelper_( + opt_fn || arguments.callee.caller, []); + } + return stack; +}; + + +/** + * Private helper for getStacktrace(). + * @param {Function} fn Function to start getting the trace from. + * @param {Array} visited List of functions visited so far. + * @return {string} Stack trace starting from function fn. + * @suppress {es5Strict} + * @private + */ +goog.debug.getStacktraceHelper_ = function(fn, visited) { + var sb = []; + + // Circular reference, certain functions like bind seem to cause a recursive + // loop so we need to catch circular references + if (goog.array.contains(visited, fn)) { + sb.push('[...circular reference...]'); + + // Traverse the call stack until function not found or max depth is reached + } else if (fn && visited.length < goog.debug.MAX_STACK_DEPTH) { + sb.push(goog.debug.getFunctionName(fn) + '('); + var args = fn.arguments; + // Args may be null for some special functions such as host objects or eval. + for (var i = 0; args && i < args.length; i++) { + if (i > 0) { + sb.push(', '); + } + var argDesc; + var arg = args[i]; + switch (typeof arg) { + case 'object': + argDesc = arg ? 'object' : 'null'; + break; + + case 'string': + argDesc = arg; + break; + + case 'number': + argDesc = String(arg); + break; + + case 'boolean': + argDesc = arg ? 'true' : 'false'; + break; + + case 'function': + argDesc = goog.debug.getFunctionName(arg); + argDesc = argDesc ? argDesc : '[fn]'; + break; + + case 'undefined': + default: + argDesc = typeof arg; + break; + } + + if (argDesc.length > 40) { + argDesc = argDesc.substr(0, 40) + '...'; + } + sb.push(argDesc); + } + visited.push(fn); + sb.push(')\n'); + /** @preserveTry */ + try { + sb.push(goog.debug.getStacktraceHelper_(fn.caller, visited)); + } catch (e) { + sb.push('[exception trying to get caller]\n'); + } + + } else if (fn) { + sb.push('[...long stack...]'); + } else { + sb.push('[end]'); + } + return sb.join(''); +}; + + +/** + * Set a custom function name resolver. + * @param {function(Function): string} resolver Resolves functions to their + * names. + */ +goog.debug.setFunctionResolver = function(resolver) { + goog.debug.fnNameResolver_ = resolver; +}; + + +/** + * Gets a function name + * @param {Function} fn Function to get name of. + * @return {string} Function's name. + */ +goog.debug.getFunctionName = function(fn) { + if (goog.debug.fnNameCache_[fn]) { + return goog.debug.fnNameCache_[fn]; + } + if (goog.debug.fnNameResolver_) { + var name = goog.debug.fnNameResolver_(fn); + if (name) { + goog.debug.fnNameCache_[fn] = name; + return name; + } + } + + // Heuristically determine function name based on code. + var functionSource = String(fn); + if (!goog.debug.fnNameCache_[functionSource]) { + var matches = /function ([^\(]+)/.exec(functionSource); + if (matches) { + var method = matches[1]; + goog.debug.fnNameCache_[functionSource] = method; + } else { + goog.debug.fnNameCache_[functionSource] = '[Anonymous]'; + } + } + + return goog.debug.fnNameCache_[functionSource]; +}; + + +/** + * Makes whitespace visible by replacing it with printable characters. + * This is useful in finding diffrences between the expected and the actual + * output strings of a testcase. + * @param {string} string whose whitespace needs to be made visible. + * @return {string} string whose whitespace is made visible. + */ +goog.debug.makeWhitespaceVisible = function(string) { + return string.replace(/ /g, '[_]') + .replace(/\f/g, '[f]') + .replace(/\n/g, '[n]\n') + .replace(/\r/g, '[r]') + .replace(/\t/g, '[t]'); +}; + + +/** + * Hash map for storing function names that have already been looked up. + * @type {Object} + * @private + */ +goog.debug.fnNameCache_ = {}; + + +/** + * Resolves functions to their names. Resolved function names will be cached. + * @type {function(Function):string} + * @private + */ +goog.debug.fnNameResolver_; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/entrypointregistry.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/entrypointregistry.js new file mode 100644 index 0000000..9394f7f --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/entrypointregistry.js @@ -0,0 +1,158 @@ +// Copyright 2010 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A global registry for entry points into a program, + * so that they can be instrumented. Each module should register their + * entry points with this registry. Designed to be compiled out + * if no instrumentation is requested. + * + * Entry points may be registered before or after a call to + * goog.debug.entryPointRegistry.monitorAll. If an entry point is registered + * later, the existing monitor will instrument the new entry point. + * + * @author nicksantos@google.com (Nick Santos) + */ + +goog.provide('goog.debug.EntryPointMonitor'); +goog.provide('goog.debug.entryPointRegistry'); + +goog.require('goog.asserts'); + + + +/** + * @interface + */ +goog.debug.EntryPointMonitor = function() {}; + + +/** + * Instruments a function. + * + * @param {!Function} fn A function to instrument. + * @return {!Function} The instrumented function. + */ +goog.debug.EntryPointMonitor.prototype.wrap; + + +/** + * Try to remove an instrumentation wrapper created by this monitor. + * If the function passed to unwrap is not a wrapper created by this + * monitor, then we will do nothing. + * + * Notice that some wrappers may not be unwrappable. For example, if other + * monitors have applied their own wrappers, then it will be impossible to + * unwrap them because their wrappers will have captured our wrapper. + * + * So it is important that entry points are unwrapped in the reverse + * order that they were wrapped. + * + * @param {!Function} fn A function to unwrap. + * @return {!Function} The unwrapped function, or {@code fn} if it was not + * a wrapped function created by this monitor. + */ +goog.debug.EntryPointMonitor.prototype.unwrap; + + +/** + * An array of entry point callbacks. + * @type {!Array.<function(!Function)>} + * @private + */ +goog.debug.entryPointRegistry.refList_ = []; + + +/** + * Monitors that should wrap all the entry points. + * @type {!Array.<!goog.debug.EntryPointMonitor>} + * @private + */ +goog.debug.entryPointRegistry.monitors_ = []; + + +/** + * Whether goog.debug.entryPointRegistry.monitorAll has ever been called. + * Checking this allows the compiler to optimize out the registrations. + * @type {boolean} + * @private + */ +goog.debug.entryPointRegistry.monitorsMayExist_ = false; + + +/** + * Register an entry point with this module. + * + * The entry point will be instrumented when a monitor is passed to + * goog.debug.entryPointRegistry.monitorAll. If this has already occurred, the + * entry point is instrumented immediately. + * + * @param {function(!Function)} callback A callback function which is called + * with a transforming function to instrument the entry point. The callback + * is responsible for wrapping the relevant entry point with the + * transforming function. + */ +goog.debug.entryPointRegistry.register = function(callback) { + // Don't use push(), so that this can be compiled out. + goog.debug.entryPointRegistry.refList_[ + goog.debug.entryPointRegistry.refList_.length] = callback; + // If no one calls monitorAll, this can be compiled out. + if (goog.debug.entryPointRegistry.monitorsMayExist_) { + var monitors = goog.debug.entryPointRegistry.monitors_; + for (var i = 0; i < monitors.length; i++) { + callback(goog.bind(monitors[i].wrap, monitors[i])); + } + } +}; + + +/** + * Configures a monitor to wrap all entry points. + * + * Entry points that have already been registered are immediately wrapped by + * the monitor. When an entry point is registered in the future, it will also + * be wrapped by the monitor when it is registered. + * + * @param {!goog.debug.EntryPointMonitor} monitor An entry point monitor. + */ +goog.debug.entryPointRegistry.monitorAll = function(monitor) { + goog.debug.entryPointRegistry.monitorsMayExist_ = true; + var transformer = goog.bind(monitor.wrap, monitor); + for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) { + goog.debug.entryPointRegistry.refList_[i](transformer); + } + goog.debug.entryPointRegistry.monitors_.push(monitor); +}; + + +/** + * Try to unmonitor all the entry points that have already been registered. If + * an entry point is registered in the future, it will not be wrapped by the + * monitor when it is registered. Note that this may fail if the entry points + * have additional wrapping. + * + * @param {!goog.debug.EntryPointMonitor} monitor The last monitor to wrap + * the entry points. + * @throws {Error} If the monitor is not the most recently configured monitor. + */ +goog.debug.entryPointRegistry.unmonitorAllIfPossible = function(monitor) { + var monitors = goog.debug.entryPointRegistry.monitors_; + goog.asserts.assert(monitor == monitors[monitors.length - 1], + 'Only the most recent monitor can be unwrapped.'); + var transformer = goog.bind(monitor.unwrap, monitor); + for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) { + goog.debug.entryPointRegistry.refList_[i](transformer); + } + monitors.length--; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/error.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/error.js new file mode 100644 index 0000000..6fed470 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/error.js @@ -0,0 +1,54 @@ +// Copyright 2009 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provides a base class for custom Error objects such that the + * stack is correctly maintained. + * + * You should never need to throw goog.debug.Error(msg) directly, Error(msg) is + * sufficient. + * + */ + +goog.provide('goog.debug.Error'); + + + +/** + * Base class for custom error objects. + * @param {*=} opt_msg The message associated with the error. + * @constructor + * @extends {Error} + */ +goog.debug.Error = function(opt_msg) { + + // Attempt to ensure there is a stack trace. + if (Error.captureStackTrace) { + Error.captureStackTrace(this, goog.debug.Error); + } else { + var stack = new Error().stack; + if (stack) { + this.stack = stack; + } + } + + if (opt_msg) { + this.message = String(opt_msg); + } +}; +goog.inherits(goog.debug.Error, Error); + + +/** @override */ +goog.debug.Error.prototype.name = 'CustomError'; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/errorhandlerweakdep.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/errorhandlerweakdep.js new file mode 100644 index 0000000..05a2b16 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/errorhandlerweakdep.js @@ -0,0 +1,38 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview File which defines dummy object to work around undefined + * properties compiler warning for weak dependencies on + * {@link goog.debug.ErrorHandler#protectEntryPoint}. + * + */ + +goog.provide('goog.debug.errorHandlerWeakDep'); + + +/** + * Dummy object to work around undefined properties compiler warning. + * @type {Object} + */ +goog.debug.errorHandlerWeakDep = { + /** + * @param {Function} fn An entry point function to be protected. + * @param {boolean=} opt_tracers Whether to install tracers around the + * fn. + * @return {Function} A protected wrapper function that calls the + * entry point function. + */ + protectEntryPoint: function(fn, opt_tracers) { return fn; } +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logbuffer.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logbuffer.js new file mode 100644 index 0000000..d2ded20 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logbuffer.js @@ -0,0 +1,148 @@ +// Copyright 2010 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A buffer for log records. The purpose of this is to improve + * logging performance by re-using old objects when the buffer becomes full and + * to eliminate the need for each app to implement their own log buffer. The + * disadvantage to doing this is that log handlers cannot maintain references to + * log records and expect that they are not overwriten at a later point. + * + * @author agrieve@google.com (Andrew Grieve) + */ + +goog.provide('goog.debug.LogBuffer'); + +goog.require('goog.asserts'); +goog.require('goog.debug.LogRecord'); + + + +/** + * Creates the log buffer. + * @constructor + * @final + */ +goog.debug.LogBuffer = function() { + goog.asserts.assert(goog.debug.LogBuffer.isBufferingEnabled(), + 'Cannot use goog.debug.LogBuffer without defining ' + + 'goog.debug.LogBuffer.CAPACITY.'); + this.clear(); +}; + + +/** + * A static method that always returns the same instance of LogBuffer. + * @return {!goog.debug.LogBuffer} The LogBuffer singleton instance. + */ +goog.debug.LogBuffer.getInstance = function() { + if (!goog.debug.LogBuffer.instance_) { + // This function is written with the return statement after the assignment + // to avoid the jscompiler StripCode bug described in http://b/2608064. + // After that bug is fixed this can be refactored. + goog.debug.LogBuffer.instance_ = new goog.debug.LogBuffer(); + } + return goog.debug.LogBuffer.instance_; +}; + + +/** + * @define {number} The number of log records to buffer. 0 means disable + * buffering. + */ +goog.define('goog.debug.LogBuffer.CAPACITY', 0); + + +/** + * The array to store the records. + * @type {!Array.<!goog.debug.LogRecord|undefined>} + * @private + */ +goog.debug.LogBuffer.prototype.buffer_; + + +/** + * The index of the most recently added record or -1 if there are no records. + * @type {number} + * @private + */ +goog.debug.LogBuffer.prototype.curIndex_; + + +/** + * Whether the buffer is at capacity. + * @type {boolean} + * @private + */ +goog.debug.LogBuffer.prototype.isFull_; + + +/** + * Adds a log record to the buffer, possibly overwriting the oldest record. + * @param {goog.debug.Logger.Level} level One of the level identifiers. + * @param {string} msg The string message. + * @param {string} loggerName The name of the source logger. + * @return {!goog.debug.LogRecord} The log record. + */ +goog.debug.LogBuffer.prototype.addRecord = function(level, msg, loggerName) { + var curIndex = (this.curIndex_ + 1) % goog.debug.LogBuffer.CAPACITY; + this.curIndex_ = curIndex; + if (this.isFull_) { + var ret = this.buffer_[curIndex]; + ret.reset(level, msg, loggerName); + return ret; + } + this.isFull_ = curIndex == goog.debug.LogBuffer.CAPACITY - 1; + return this.buffer_[curIndex] = + new goog.debug.LogRecord(level, msg, loggerName); +}; + + +/** + * @return {boolean} Whether the log buffer is enabled. + */ +goog.debug.LogBuffer.isBufferingEnabled = function() { + return goog.debug.LogBuffer.CAPACITY > 0; +}; + + +/** + * Removes all buffered log records. + */ +goog.debug.LogBuffer.prototype.clear = function() { + this.buffer_ = new Array(goog.debug.LogBuffer.CAPACITY); + this.curIndex_ = -1; + this.isFull_ = false; +}; + + +/** + * Calls the given function for each buffered log record, starting with the + * oldest one. + * @param {function(!goog.debug.LogRecord)} func The function to call. + */ +goog.debug.LogBuffer.prototype.forEachRecord = function(func) { + var buffer = this.buffer_; + // Corner case: no records. + if (!buffer[0]) { + return; + } + var curIndex = this.curIndex_; + var i = this.isFull_ ? curIndex : -1; + do { + i = (i + 1) % goog.debug.LogBuffer.CAPACITY; + func(/** @type {!goog.debug.LogRecord} */ (buffer[i])); + } while (i != curIndex); +}; + diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logger.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logger.js new file mode 100644 index 0000000..1e05922 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logger.js @@ -0,0 +1,885 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Definition of the Logger class. Please minimize dependencies + * this file has on other closure classes as any dependency it takes won't be + * able to use the logging infrastructure. + * + * @see ../demos/debug.html + */ + +goog.provide('goog.debug.LogManager'); +goog.provide('goog.debug.Loggable'); +goog.provide('goog.debug.Logger'); +goog.provide('goog.debug.Logger.Level'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.debug'); +goog.require('goog.debug.LogBuffer'); +goog.require('goog.debug.LogRecord'); + + +/** + * A message value that can be handled by a Logger. + * + * Functions are treated like callbacks, but are only called when the event's + * log level is enabled. This is useful for logging messages that are expensive + * to construct. + * + * @typedef {string|function(): string} + */ +goog.debug.Loggable; + + + +/** + * The Logger is an object used for logging debug messages. Loggers are + * normally named, using a hierarchical dot-separated namespace. Logger names + * can be arbitrary strings, but they should normally be based on the package + * name or class name of the logged component, such as goog.net.BrowserChannel. + * + * The Logger object is loosely based on the java class + * java.util.logging.Logger. It supports different levels of filtering for + * different loggers. + * + * The logger object should never be instantiated by application code. It + * should always use the goog.debug.Logger.getLogger function. + * + * @constructor + * @param {string} name The name of the Logger. + * @final + */ +goog.debug.Logger = function(name) { + /** + * Name of the Logger. Generally a dot-separated namespace + * @private {string} + */ + this.name_ = name; + + /** + * Parent Logger. + * @private {goog.debug.Logger} + */ + this.parent_ = null; + + /** + * Level that this logger only filters above. Null indicates it should + * inherit from the parent. + * @private {goog.debug.Logger.Level} + */ + this.level_ = null; + + /** + * Map of children loggers. The keys are the leaf names of the children and + * the values are the child loggers. + * @private {Object} + */ + this.children_ = null; + + /** + * Handlers that are listening to this logger. + * @private {Array.<Function>} + */ + this.handlers_ = null; +}; + + +/** @const */ +goog.debug.Logger.ROOT_LOGGER_NAME = ''; + + +/** + * @define {boolean} Toggles whether loggers other than the root logger can have + * log handlers attached to them and whether they can have their log level + * set. Logging is a bit faster when this is set to false. + */ +goog.define('goog.debug.Logger.ENABLE_HIERARCHY', true); + + +if (!goog.debug.Logger.ENABLE_HIERARCHY) { + /** + * @type {!Array.<Function>} + * @private + */ + goog.debug.Logger.rootHandlers_ = []; + + + /** + * @type {goog.debug.Logger.Level} + * @private + */ + goog.debug.Logger.rootLevel_; +} + + + +/** + * The Level class defines a set of standard logging levels that + * can be used to control logging output. The logging Level objects + * are ordered and are specified by ordered integers. Enabling logging + * at a given level also enables logging at all higher levels. + * <p> + * Clients should normally use the predefined Level constants such + * as Level.SEVERE. + * <p> + * The levels in descending order are: + * <ul> + * <li>SEVERE (highest value) + * <li>WARNING + * <li>INFO + * <li>CONFIG + * <li>FINE + * <li>FINER + * <li>FINEST (lowest value) + * </ul> + * In addition there is a level OFF that can be used to turn + * off logging, and a level ALL that can be used to enable + * logging of all messages. + * + * @param {string} name The name of the level. + * @param {number} value The numeric value of the level. + * @constructor + * @final + */ +goog.debug.Logger.Level = function(name, value) { + /** + * The name of the level + * @type {string} + */ + this.name = name; + + /** + * The numeric value of the level + * @type {number} + */ + this.value = value; +}; + + +/** + * @return {string} String representation of the logger level. + * @override + */ +goog.debug.Logger.Level.prototype.toString = function() { + return this.name; +}; + + +/** + * OFF is a special level that can be used to turn off logging. + * This level is initialized to <CODE>Infinity</CODE>. + * @type {!goog.debug.Logger.Level} + */ +goog.debug.Logger.Level.OFF = + new goog.debug.Logger.Level('OFF', Infinity); + + +/** + * SHOUT is a message level for extra debugging loudness. + * This level is initialized to <CODE>1200</CODE>. + * @type {!goog.debug.Logger.Level} + */ +goog.debug.Logger.Level.SHOUT = new goog.debug.Logger.Level('SHOUT', 1200); + + +/** + * SEVERE is a message level indicating a serious failure. + * This level is initialized to <CODE>1000</CODE>. + * @type {!goog.debug.Logger.Level} + */ +goog.debug.Logger.Level.SEVERE = new goog.debug.Logger.Level('SEVERE', 1000); + + +/** + * WARNING is a message level indicating a potential problem. + * This level is initialized to <CODE>900</CODE>. + * @type {!goog.debug.Logger.Level} + */ +goog.debug.Logger.Level.WARNING = new goog.debug.Logger.Level('WARNING', 900); + + +/** + * INFO is a message level for informational messages. + * This level is initialized to <CODE>800</CODE>. + * @type {!goog.debug.Logger.Level} + */ +goog.debug.Logger.Level.INFO = new goog.debug.Logger.Level('INFO', 800); + + +/** + * CONFIG is a message level for static configuration messages. + * This level is initialized to <CODE>700</CODE>. + * @type {!goog.debug.Logger.Level} + */ +goog.debug.Logger.Level.CONFIG = new goog.debug.Logger.Level('CONFIG', 700); + + +/** + * FINE is a message level providing tracing information. + * This level is initialized to <CODE>500</CODE>. + * @type {!goog.debug.Logger.Level} + */ +goog.debug.Logger.Level.FINE = new goog.debug.Logger.Level('FINE', 500); + + +/** + * FINER indicates a fairly detailed tracing message. + * This level is initialized to <CODE>400</CODE>. + * @type {!goog.debug.Logger.Level} + */ +goog.debug.Logger.Level.FINER = new goog.debug.Logger.Level('FINER', 400); + +/** + * FINEST indicates a highly detailed tracing message. + * This level is initialized to <CODE>300</CODE>. + * @type {!goog.debug.Logger.Level} + */ + +goog.debug.Logger.Level.FINEST = new goog.debug.Logger.Level('FINEST', 300); + + +/** + * ALL indicates that all messages should be logged. + * This level is initialized to <CODE>0</CODE>. + * @type {!goog.debug.Logger.Level} + */ +goog.debug.Logger.Level.ALL = new goog.debug.Logger.Level('ALL', 0); + + +/** + * The predefined levels. + * @type {!Array.<!goog.debug.Logger.Level>} + * @final + */ +goog.debug.Logger.Level.PREDEFINED_LEVELS = [ + goog.debug.Logger.Level.OFF, + goog.debug.Logger.Level.SHOUT, + goog.debug.Logger.Level.SEVERE, + goog.debug.Logger.Level.WARNING, + goog.debug.Logger.Level.INFO, + goog.debug.Logger.Level.CONFIG, + goog.debug.Logger.Level.FINE, + goog.debug.Logger.Level.FINER, + goog.debug.Logger.Level.FINEST, + goog.debug.Logger.Level.ALL]; + + +/** + * A lookup map used to find the level object based on the name or value of + * the level object. + * @type {Object} + * @private + */ +goog.debug.Logger.Level.predefinedLevelsCache_ = null; + + +/** + * Creates the predefined levels cache and populates it. + * @private + */ +goog.debug.Logger.Level.createPredefinedLevelsCache_ = function() { + goog.debug.Logger.Level.predefinedLevelsCache_ = {}; + for (var i = 0, level; level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i]; + i++) { + goog.debug.Logger.Level.predefinedLevelsCache_[level.value] = level; + goog.debug.Logger.Level.predefinedLevelsCache_[level.name] = level; + } +}; + + +/** + * Gets the predefined level with the given name. + * @param {string} name The name of the level. + * @return {goog.debug.Logger.Level} The level, or null if none found. + */ +goog.debug.Logger.Level.getPredefinedLevel = function(name) { + if (!goog.debug.Logger.Level.predefinedLevelsCache_) { + goog.debug.Logger.Level.createPredefinedLevelsCache_(); + } + + return goog.debug.Logger.Level.predefinedLevelsCache_[name] || null; +}; + + +/** + * Gets the highest predefined level <= #value. + * @param {number} value Level value. + * @return {goog.debug.Logger.Level} The level, or null if none found. + */ +goog.debug.Logger.Level.getPredefinedLevelByValue = function(value) { + if (!goog.debug.Logger.Level.predefinedLevelsCache_) { + goog.debug.Logger.Level.createPredefinedLevelsCache_(); + } + + if (value in goog.debug.Logger.Level.predefinedLevelsCache_) { + return goog.debug.Logger.Level.predefinedLevelsCache_[value]; + } + + for (var i = 0; i < goog.debug.Logger.Level.PREDEFINED_LEVELS.length; ++i) { + var level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i]; + if (level.value <= value) { + return level; + } + } + return null; +}; + + +/** + * Finds or creates a logger for a named subsystem. If a logger has already been + * created with the given name it is returned. Otherwise a new logger is + * created. If a new logger is created its log level will be configured based + * on the LogManager configuration and it will configured to also send logging + * output to its parent's handlers. It will be registered in the LogManager + * global namespace. + * + * @param {string} name A name for the logger. This should be a dot-separated + * name and should normally be based on the package name or class name of the + * subsystem, such as goog.net.BrowserChannel. + * @return {!goog.debug.Logger} The named logger. + * @deprecated use goog.log instead. http://go/goog-debug-logger-deprecated + */ +goog.debug.Logger.getLogger = function(name) { + return goog.debug.LogManager.getLogger(name); +}; + + +/** + * Logs a message to profiling tools, if available. + * {@see https://developers.google.com/web-toolkit/speedtracer/logging-api} + * {@see http://msdn.microsoft.com/en-us/library/dd433074(VS.85).aspx} + * @param {string} msg The message to log. + */ +goog.debug.Logger.logToProfilers = function(msg) { + // Using goog.global, as loggers might be used in window-less contexts. + if (goog.global['console']) { + if (goog.global['console']['timeStamp']) { + // Logs a message to Firebug, Web Inspector, SpeedTracer, etc. + goog.global['console']['timeStamp'](msg); + } else if (goog.global['console']['markTimeline']) { + // TODO(user): markTimeline is deprecated. Drop this else clause entirely + // after Chrome M14 hits stable. + goog.global['console']['markTimeline'](msg); + } + } + + if (goog.global['msWriteProfilerMark']) { + // Logs a message to the Microsoft profiler + goog.global['msWriteProfilerMark'](msg); + } +}; + + +/** + * Gets the name of this logger. + * @return {string} The name of this logger. + */ +goog.debug.Logger.prototype.getName = function() { + return this.name_; +}; + + +/** + * Adds a handler to the logger. This doesn't use the event system because + * we want to be able to add logging to the event system. + * @param {Function} handler Handler function to add. + */ +goog.debug.Logger.prototype.addHandler = function(handler) { + if (goog.debug.LOGGING_ENABLED) { + if (goog.debug.Logger.ENABLE_HIERARCHY) { + if (!this.handlers_) { + this.handlers_ = []; + } + this.handlers_.push(handler); + } else { + goog.asserts.assert(!this.name_, + 'Cannot call addHandler on a non-root logger when ' + + 'goog.debug.Logger.ENABLE_HIERARCHY is false.'); + goog.debug.Logger.rootHandlers_.push(handler); + } + } +}; + + +/** + * Removes a handler from the logger. This doesn't use the event system because + * we want to be able to add logging to the event system. + * @param {Function} handler Handler function to remove. + * @return {boolean} Whether the handler was removed. + */ +goog.debug.Logger.prototype.removeHandler = function(handler) { + if (goog.debug.LOGGING_ENABLED) { + var handlers = goog.debug.Logger.ENABLE_HIERARCHY ? this.handlers_ : + goog.debug.Logger.rootHandlers_; + return !!handlers && goog.array.remove(handlers, handler); + } else { + return false; + } +}; + + +/** + * Returns the parent of this logger. + * @return {goog.debug.Logger} The parent logger or null if this is the root. + */ +goog.debug.Logger.prototype.getParent = function() { + return this.parent_; +}; + + +/** + * Returns the children of this logger as a map of the child name to the logger. + * @return {!Object} The map where the keys are the child leaf names and the + * values are the Logger objects. + */ +goog.debug.Logger.prototype.getChildren = function() { + if (!this.children_) { + this.children_ = {}; + } + return this.children_; +}; + + +/** + * Set the log level specifying which message levels will be logged by this + * logger. Message levels lower than this value will be discarded. + * The level value Level.OFF can be used to turn off logging. If the new level + * is null, it means that this node should inherit its level from its nearest + * ancestor with a specific (non-null) level value. + * + * @param {goog.debug.Logger.Level} level The new level. + */ +goog.debug.Logger.prototype.setLevel = function(level) { + if (goog.debug.LOGGING_ENABLED) { + if (goog.debug.Logger.ENABLE_HIERARCHY) { + this.level_ = level; + } else { + goog.asserts.assert(!this.name_, + 'Cannot call setLevel() on a non-root logger when ' + + 'goog.debug.Logger.ENABLE_HIERARCHY is false.'); + goog.debug.Logger.rootLevel_ = level; + } + } +}; + + +/** + * Gets the log level specifying which message levels will be logged by this + * logger. Message levels lower than this value will be discarded. + * The level value Level.OFF can be used to turn off logging. If the level + * is null, it means that this node should inherit its level from its nearest + * ancestor with a specific (non-null) level value. + * + * @return {goog.debug.Logger.Level} The level. + */ +goog.debug.Logger.prototype.getLevel = function() { + return goog.debug.LOGGING_ENABLED ? + this.level_ : goog.debug.Logger.Level.OFF; +}; + + +/** + * Returns the effective level of the logger based on its ancestors' levels. + * @return {goog.debug.Logger.Level} The level. + */ +goog.debug.Logger.prototype.getEffectiveLevel = function() { + if (!goog.debug.LOGGING_ENABLED) { + return goog.debug.Logger.Level.OFF; + } + + if (!goog.debug.Logger.ENABLE_HIERARCHY) { + return goog.debug.Logger.rootLevel_; + } + if (this.level_) { + return this.level_; + } + if (this.parent_) { + return this.parent_.getEffectiveLevel(); + } + goog.asserts.fail('Root logger has no level set.'); + return null; +}; + + +/** + * Checks if a message of the given level would actually be logged by this + * logger. This check is based on the Loggers effective level, which may be + * inherited from its parent. + * @param {goog.debug.Logger.Level} level The level to check. + * @return {boolean} Whether the message would be logged. + */ +goog.debug.Logger.prototype.isLoggable = function(level) { + return goog.debug.LOGGING_ENABLED && + level.value >= this.getEffectiveLevel().value; +}; + + +/** + * Logs a message. If the logger is currently enabled for the + * given message level then the given message is forwarded to all the + * registered output Handler objects. + * @param {goog.debug.Logger.Level} level One of the level identifiers. + * @param {goog.debug.Loggable} msg The message to log. + * @param {Error|Object=} opt_exception An exception associated with the + * message. + */ +goog.debug.Logger.prototype.log = function(level, msg, opt_exception) { + // java caches the effective level, not sure it's necessary here + if (goog.debug.LOGGING_ENABLED && this.isLoggable(level)) { + // Message callbacks can be useful when a log message is expensive to build. + if (goog.isFunction(msg)) { + msg = msg(); + } + + this.doLogRecord_(this.getLogRecord( + level, msg, opt_exception, goog.debug.Logger.prototype.log)); + } +}; + + +/** + * Creates a new log record and adds the exception (if present) to it. + * @param {goog.debug.Logger.Level} level One of the level identifiers. + * @param {string} msg The string message. + * @param {Error|Object=} opt_exception An exception associated with the + * message. + * @param {Function=} opt_fnStackContext A function to use as the base + * of the stack trace used in the log record. + * @return {!goog.debug.LogRecord} A log record. + * @suppress {es5Strict} + */ +goog.debug.Logger.prototype.getLogRecord = function( + level, msg, opt_exception, opt_fnStackContext) { + if (goog.debug.LogBuffer.isBufferingEnabled()) { + var logRecord = + goog.debug.LogBuffer.getInstance().addRecord(level, msg, this.name_); + } else { + logRecord = new goog.debug.LogRecord(level, String(msg), this.name_); + } + if (opt_exception) { + var context; + if (goog.STRICT_MODE_COMPATIBLE) { + context = opt_fnStackContext || goog.debug.Logger.prototype.getLogRecord; + } else { + context = opt_fnStackContext || arguments.callee.caller; + } + + logRecord.setException(opt_exception); + logRecord.setExceptionText( + goog.debug.exposeException(opt_exception, + opt_fnStackContext || goog.debug.Logger.prototype.getLogRecord)); + } + return logRecord; +}; + + +/** + * Logs a message at the Logger.Level.SHOUT level. + * If the logger is currently enabled for the given message level then the + * given message is forwarded to all the registered output Handler objects. + * @param {goog.debug.Loggable} msg The message to log. + * @param {Error=} opt_exception An exception associated with the message. + */ +goog.debug.Logger.prototype.shout = function(msg, opt_exception) { + if (goog.debug.LOGGING_ENABLED) { + this.log(goog.debug.Logger.Level.SHOUT, msg, opt_exception); + } +}; + + +/** + * Logs a message at the Logger.Level.SEVERE level. + * If the logger is currently enabled for the given message level then the + * given message is forwarded to all the registered output Handler objects. + * @param {goog.debug.Loggable} msg The message to log. + * @param {Error=} opt_exception An exception associated with the message. + */ +goog.debug.Logger.prototype.severe = function(msg, opt_exception) { + if (goog.debug.LOGGING_ENABLED) { + this.log(goog.debug.Logger.Level.SEVERE, msg, opt_exception); + } +}; + + +/** + * Logs a message at the Logger.Level.WARNING level. + * If the logger is currently enabled for the given message level then the + * given message is forwarded to all the registered output Handler objects. + * @param {goog.debug.Loggable} msg The message to log. + * @param {Error=} opt_exception An exception associated with the message. + */ +goog.debug.Logger.prototype.warning = function(msg, opt_exception) { + if (goog.debug.LOGGING_ENABLED) { + this.log(goog.debug.Logger.Level.WARNING, msg, opt_exception); + } +}; + + +/** + * Logs a message at the Logger.Level.INFO level. + * If the logger is currently enabled for the given message level then the + * given message is forwarded to all the registered output Handler objects. + * @param {goog.debug.Loggable} msg The message to log. + * @param {Error=} opt_exception An exception associated with the message. + */ +goog.debug.Logger.prototype.info = function(msg, opt_exception) { + if (goog.debug.LOGGING_ENABLED) { + this.log(goog.debug.Logger.Level.INFO, msg, opt_exception); + } +}; + + +/** + * Logs a message at the Logger.Level.CONFIG level. + * If the logger is currently enabled for the given message level then the + * given message is forwarded to all the registered output Handler objects. + * @param {goog.debug.Loggable} msg The message to log. + * @param {Error=} opt_exception An exception associated with the message. + */ +goog.debug.Logger.prototype.config = function(msg, opt_exception) { + if (goog.debug.LOGGING_ENABLED) { + this.log(goog.debug.Logger.Level.CONFIG, msg, opt_exception); + } +}; + + +/** + * Logs a message at the Logger.Level.FINE level. + * If the logger is currently enabled for the given message level then the + * given message is forwarded to all the registered output Handler objects. + * @param {goog.debug.Loggable} msg The message to log. + * @param {Error=} opt_exception An exception associated with the message. + */ +goog.debug.Logger.prototype.fine = function(msg, opt_exception) { + if (goog.debug.LOGGING_ENABLED) { + this.log(goog.debug.Logger.Level.FINE, msg, opt_exception); + } +}; + + +/** + * Logs a message at the Logger.Level.FINER level. + * If the logger is currently enabled for the given message level then the + * given message is forwarded to all the registered output Handler objects. + * @param {goog.debug.Loggable} msg The message to log. + * @param {Error=} opt_exception An exception associated with the message. + */ +goog.debug.Logger.prototype.finer = function(msg, opt_exception) { + if (goog.debug.LOGGING_ENABLED) { + this.log(goog.debug.Logger.Level.FINER, msg, opt_exception); + } +}; + + +/** + * Logs a message at the Logger.Level.FINEST level. + * If the logger is currently enabled for the given message level then the + * given message is forwarded to all the registered output Handler objects. + * @param {goog.debug.Loggable} msg The message to log. + * @param {Error=} opt_exception An exception associated with the message. + */ +goog.debug.Logger.prototype.finest = function(msg, opt_exception) { + if (goog.debug.LOGGING_ENABLED) { + this.log(goog.debug.Logger.Level.FINEST, msg, opt_exception); + } +}; + + +/** + * Logs a LogRecord. If the logger is currently enabled for the + * given message level then the given message is forwarded to all the + * registered output Handler objects. + * @param {goog.debug.LogRecord} logRecord A log record to log. + */ +goog.debug.Logger.prototype.logRecord = function(logRecord) { + if (goog.debug.LOGGING_ENABLED && this.isLoggable(logRecord.getLevel())) { + this.doLogRecord_(logRecord); + } +}; + + +/** + * Logs a LogRecord. + * @param {goog.debug.LogRecord} logRecord A log record to log. + * @private + */ +goog.debug.Logger.prototype.doLogRecord_ = function(logRecord) { + goog.debug.Logger.logToProfilers('log:' + logRecord.getMessage()); + if (goog.debug.Logger.ENABLE_HIERARCHY) { + var target = this; + while (target) { + target.callPublish_(logRecord); + target = target.getParent(); + } + } else { + for (var i = 0, handler; handler = goog.debug.Logger.rootHandlers_[i++]; ) { + handler(logRecord); + } + } +}; + + +/** + * Calls the handlers for publish. + * @param {goog.debug.LogRecord} logRecord The log record to publish. + * @private + */ +goog.debug.Logger.prototype.callPublish_ = function(logRecord) { + if (this.handlers_) { + for (var i = 0, handler; handler = this.handlers_[i]; i++) { + handler(logRecord); + } + } +}; + + +/** + * Sets the parent of this logger. This is used for setting up the logger tree. + * @param {goog.debug.Logger} parent The parent logger. + * @private + */ +goog.debug.Logger.prototype.setParent_ = function(parent) { + this.parent_ = parent; +}; + + +/** + * Adds a child to this logger. This is used for setting up the logger tree. + * @param {string} name The leaf name of the child. + * @param {goog.debug.Logger} logger The child logger. + * @private + */ +goog.debug.Logger.prototype.addChild_ = function(name, logger) { + this.getChildren()[name] = logger; +}; + + +/** + * There is a single global LogManager object that is used to maintain a set of + * shared state about Loggers and log services. This is loosely based on the + * java class java.util.logging.LogManager. + */ +goog.debug.LogManager = {}; + + +/** + * Map of logger names to logger objects. + * + * @type {!Object.<string, !goog.debug.Logger>} + * @private + */ +goog.debug.LogManager.loggers_ = {}; + + +/** + * The root logger which is the root of the logger tree. + * @type {goog.debug.Logger} + * @private + */ +goog.debug.LogManager.rootLogger_ = null; + + +/** + * Initializes the LogManager if not already initialized. + */ +goog.debug.LogManager.initialize = function() { + if (!goog.debug.LogManager.rootLogger_) { + goog.debug.LogManager.rootLogger_ = new goog.debug.Logger( + goog.debug.Logger.ROOT_LOGGER_NAME); + goog.debug.LogManager.loggers_[goog.debug.Logger.ROOT_LOGGER_NAME] = + goog.debug.LogManager.rootLogger_; + goog.debug.LogManager.rootLogger_.setLevel(goog.debug.Logger.Level.CONFIG); + } +}; + + +/** + * Returns all the loggers. + * @return {!Object.<string, !goog.debug.Logger>} Map of logger names to logger + * objects. + */ +goog.debug.LogManager.getLoggers = function() { + return goog.debug.LogManager.loggers_; +}; + + +/** + * Returns the root of the logger tree namespace, the logger with the empty + * string as its name. + * + * @return {!goog.debug.Logger} The root logger. + */ +goog.debug.LogManager.getRoot = function() { + goog.debug.LogManager.initialize(); + return /** @type {!goog.debug.Logger} */ (goog.debug.LogManager.rootLogger_); +}; + + +/** + * Finds a named logger. + * + * @param {string} name A name for the logger. This should be a dot-separated + * name and should normally be based on the package name or class name of the + * subsystem, such as goog.net.BrowserChannel. + * @return {!goog.debug.Logger} The named logger. + */ +goog.debug.LogManager.getLogger = function(name) { + goog.debug.LogManager.initialize(); + var ret = goog.debug.LogManager.loggers_[name]; + return ret || goog.debug.LogManager.createLogger_(name); +}; + + +/** + * Creates a function that can be passed to goog.debug.catchErrors. The function + * will log all reported errors using the given logger. + * @param {goog.debug.Logger=} opt_logger The logger to log the errors to. + * Defaults to the root logger. + * @return {function(Object)} The created function. + */ +goog.debug.LogManager.createFunctionForCatchErrors = function(opt_logger) { + return function(info) { + var logger = opt_logger || goog.debug.LogManager.getRoot(); + logger.severe('Error: ' + info.message + ' (' + info.fileName + + ' @ Line: ' + info.line + ')'); + }; +}; + + +/** + * Creates the named logger. Will also create the parents of the named logger + * if they don't yet exist. + * @param {string} name The name of the logger. + * @return {!goog.debug.Logger} The named logger. + * @private + */ +goog.debug.LogManager.createLogger_ = function(name) { + // find parent logger + var logger = new goog.debug.Logger(name); + if (goog.debug.Logger.ENABLE_HIERARCHY) { + var lastDotIndex = name.lastIndexOf('.'); + var parentName = name.substr(0, lastDotIndex); + var leafName = name.substr(lastDotIndex + 1); + var parentLogger = goog.debug.LogManager.getLogger(parentName); + + // tell the parent about the child and the child about the parent + parentLogger.addChild_(leafName, logger); + logger.setParent_(parentLogger); + } + + goog.debug.LogManager.loggers_[name] = logger; + return logger; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logrecord.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logrecord.js new file mode 100644 index 0000000..990af98 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/debug/logrecord.js @@ -0,0 +1,271 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Definition of the LogRecord class. Please minimize + * dependencies this file has on other closure classes as any dependency it + * takes won't be able to use the logging infrastructure. + * + */ + +goog.provide('goog.debug.LogRecord'); + + + +/** + * LogRecord objects are used to pass logging requests between + * the logging framework and individual log Handlers. + * @constructor + * @param {goog.debug.Logger.Level} level One of the level identifiers. + * @param {string} msg The string message. + * @param {string} loggerName The name of the source logger. + * @param {number=} opt_time Time this log record was created if other than now. + * If 0, we use #goog.now. + * @param {number=} opt_sequenceNumber Sequence number of this log record. This + * should only be passed in when restoring a log record from persistence. + */ +goog.debug.LogRecord = function(level, msg, loggerName, + opt_time, opt_sequenceNumber) { + this.reset(level, msg, loggerName, opt_time, opt_sequenceNumber); +}; + + +/** + * Time the LogRecord was created. + * @type {number} + * @private + */ +goog.debug.LogRecord.prototype.time_; + + +/** + * Level of the LogRecord + * @type {goog.debug.Logger.Level} + * @private + */ +goog.debug.LogRecord.prototype.level_; + + +/** + * Message associated with the record + * @type {string} + * @private + */ +goog.debug.LogRecord.prototype.msg_; + + +/** + * Name of the logger that created the record. + * @type {string} + * @private + */ +goog.debug.LogRecord.prototype.loggerName_; + + +/** + * Sequence number for the LogRecord. Each record has a unique sequence number + * that is greater than all log records created before it. + * @type {number} + * @private + */ +goog.debug.LogRecord.prototype.sequenceNumber_ = 0; + + +/** + * Exception associated with the record + * @type {Object} + * @private + */ +goog.debug.LogRecord.prototype.exception_ = null; + + +/** + * Exception text associated with the record + * @type {?string} + * @private + */ +goog.debug.LogRecord.prototype.exceptionText_ = null; + + +/** + * @define {boolean} Whether to enable log sequence numbers. + */ +goog.define('goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS', true); + + +/** + * A sequence counter for assigning increasing sequence numbers to LogRecord + * objects. + * @type {number} + * @private + */ +goog.debug.LogRecord.nextSequenceNumber_ = 0; + + +/** + * Sets all fields of the log record. + * @param {goog.debug.Logger.Level} level One of the level identifiers. + * @param {string} msg The string message. + * @param {string} loggerName The name of the source logger. + * @param {number=} opt_time Time this log record was created if other than now. + * If 0, we use #goog.now. + * @param {number=} opt_sequenceNumber Sequence number of this log record. This + * should only be passed in when restoring a log record from persistence. + */ +goog.debug.LogRecord.prototype.reset = function(level, msg, loggerName, + opt_time, opt_sequenceNumber) { + if (goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS) { + this.sequenceNumber_ = typeof opt_sequenceNumber == 'number' ? + opt_sequenceNumber : goog.debug.LogRecord.nextSequenceNumber_++; + } + + this.time_ = opt_time || goog.now(); + this.level_ = level; + this.msg_ = msg; + this.loggerName_ = loggerName; + delete this.exception_; + delete this.exceptionText_; +}; + + +/** + * Get the source Logger's name. + * + * @return {string} source logger name (may be null). + */ +goog.debug.LogRecord.prototype.getLoggerName = function() { + return this.loggerName_; +}; + + +/** + * Get the exception that is part of the log record. + * + * @return {Object} the exception. + */ +goog.debug.LogRecord.prototype.getException = function() { + return this.exception_; +}; + + +/** + * Set the exception that is part of the log record. + * + * @param {Object} exception the exception. + */ +goog.debug.LogRecord.prototype.setException = function(exception) { + this.exception_ = exception; +}; + + +/** + * Get the exception text that is part of the log record. + * + * @return {?string} Exception text. + */ +goog.debug.LogRecord.prototype.getExceptionText = function() { + return this.exceptionText_; +}; + + +/** + * Set the exception text that is part of the log record. + * + * @param {string} text The exception text. + */ +goog.debug.LogRecord.prototype.setExceptionText = function(text) { + this.exceptionText_ = text; +}; + + +/** + * Get the source Logger's name. + * + * @param {string} loggerName source logger name (may be null). + */ +goog.debug.LogRecord.prototype.setLoggerName = function(loggerName) { + this.loggerName_ = loggerName; +}; + + +/** + * Get the logging message level, for example Level.SEVERE. + * @return {goog.debug.Logger.Level} the logging message level. + */ +goog.debug.LogRecord.prototype.getLevel = function() { + return this.level_; +}; + + +/** + * Set the logging message level, for example Level.SEVERE. + * @param {goog.debug.Logger.Level} level the logging message level. + */ +goog.debug.LogRecord.prototype.setLevel = function(level) { + this.level_ = level; +}; + + +/** + * Get the "raw" log message, before localization or formatting. + * + * @return {string} the raw message string. + */ +goog.debug.LogRecord.prototype.getMessage = function() { + return this.msg_; +}; + + +/** + * Set the "raw" log message, before localization or formatting. + * + * @param {string} msg the raw message string. + */ +goog.debug.LogRecord.prototype.setMessage = function(msg) { + this.msg_ = msg; +}; + + +/** + * Get event time in milliseconds since 1970. + * + * @return {number} event time in millis since 1970. + */ +goog.debug.LogRecord.prototype.getMillis = function() { + return this.time_; +}; + + +/** + * Set event time in milliseconds since 1970. + * + * @param {number} time event time in millis since 1970. + */ +goog.debug.LogRecord.prototype.setMillis = function(time) { + this.time_ = time; +}; + + +/** + * Get the sequence number. + * <p> + * Sequence numbers are normally assigned in the LogRecord + * constructor, which assigns unique sequence numbers to + * each new LogRecord in increasing order. + * @return {number} the sequence number. + */ +goog.debug.LogRecord.prototype.getSequenceNumber = function() { + return this.sequenceNumber_; +}; + diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/disposable/disposable.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/disposable/disposable.js new file mode 100644 index 0000000..c8340db --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/disposable/disposable.js @@ -0,0 +1,299 @@ +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Implements the disposable interface. The dispose method is used + * to clean up references and resources. + * @author arv@google.com (Erik Arvidsson) + */ + + +goog.provide('goog.Disposable'); +/** @suppress {extraProvide} */ +goog.provide('goog.dispose'); +/** @suppress {extraProvide} */ +goog.provide('goog.disposeAll'); + +goog.require('goog.disposable.IDisposable'); + + + +/** + * Class that provides the basic implementation for disposable objects. If your + * class holds one or more references to COM objects, DOM nodes, or other + * disposable objects, it should extend this class or implement the disposable + * interface (defined in goog.disposable.IDisposable). + * @constructor + * @implements {goog.disposable.IDisposable} + */ +goog.Disposable = function() { + if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) { + if (goog.Disposable.INCLUDE_STACK_ON_CREATION) { + this.creationStack = new Error().stack; + } + goog.Disposable.instances_[goog.getUid(this)] = this; + } +}; + + +/** + * @enum {number} Different monitoring modes for Disposable. + */ +goog.Disposable.MonitoringMode = { + /** + * No monitoring. + */ + OFF: 0, + /** + * Creating and disposing the goog.Disposable instances is monitored. All + * disposable objects need to call the {@code goog.Disposable} base + * constructor. The PERMANENT mode must be switched on before creating any + * goog.Disposable instances. + */ + PERMANENT: 1, + /** + * INTERACTIVE mode can be switched on and off on the fly without producing + * errors. It also doesn't warn if the disposable objects don't call the + * {@code goog.Disposable} base constructor. + */ + INTERACTIVE: 2 +}; + + +/** + * @define {number} The monitoring mode of the goog.Disposable + * instances. Default is OFF. Switching on the monitoring is only + * recommended for debugging because it has a significant impact on + * performance and memory usage. If switched off, the monitoring code + * compiles down to 0 bytes. + */ +goog.define('goog.Disposable.MONITORING_MODE', 0); + + +/** + * @define {boolean} Whether to attach creation stack to each created disposable + * instance; This is only relevant for when MonitoringMode != OFF. + */ +goog.define('goog.Disposable.INCLUDE_STACK_ON_CREATION', true); + + +/** + * Maps the unique ID of every undisposed {@code goog.Disposable} object to + * the object itself. + * @type {!Object.<number, !goog.Disposable>} + * @private + */ +goog.Disposable.instances_ = {}; + + +/** + * @return {!Array.<!goog.Disposable>} All {@code goog.Disposable} objects that + * haven't been disposed of. + */ +goog.Disposable.getUndisposedObjects = function() { + var ret = []; + for (var id in goog.Disposable.instances_) { + if (goog.Disposable.instances_.hasOwnProperty(id)) { + ret.push(goog.Disposable.instances_[Number(id)]); + } + } + return ret; +}; + + +/** + * Clears the registry of undisposed objects but doesn't dispose of them. + */ +goog.Disposable.clearUndisposedObjects = function() { + goog.Disposable.instances_ = {}; +}; + + +/** + * Whether the object has been disposed of. + * @type {boolean} + * @private + */ +goog.Disposable.prototype.disposed_ = false; + + +/** + * Callbacks to invoke when this object is disposed. + * @type {Array.<!Function>} + * @private + */ +goog.Disposable.prototype.onDisposeCallbacks_; + + +/** + * If monitoring the goog.Disposable instances is enabled, stores the creation + * stack trace of the Disposable instance. + * @type {string} + */ +goog.Disposable.prototype.creationStack; + + +/** + * @return {boolean} Whether the object has been disposed of. + * @override + */ +goog.Disposable.prototype.isDisposed = function() { + return this.disposed_; +}; + + +/** + * @return {boolean} Whether the object has been disposed of. + * @deprecated Use {@link #isDisposed} instead. + */ +goog.Disposable.prototype.getDisposed = goog.Disposable.prototype.isDisposed; + + +/** + * Disposes of the object. If the object hasn't already been disposed of, calls + * {@link #disposeInternal}. Classes that extend {@code goog.Disposable} should + * override {@link #disposeInternal} in order to delete references to COM + * objects, DOM nodes, and other disposable objects. Reentrant. + * + * @return {void} Nothing. + * @override + */ +goog.Disposable.prototype.dispose = function() { + if (!this.disposed_) { + // Set disposed_ to true first, in case during the chain of disposal this + // gets disposed recursively. + this.disposed_ = true; + this.disposeInternal(); + if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) { + var uid = goog.getUid(this); + if (goog.Disposable.MONITORING_MODE == + goog.Disposable.MonitoringMode.PERMANENT && + !goog.Disposable.instances_.hasOwnProperty(uid)) { + throw Error(this + ' did not call the goog.Disposable base ' + + 'constructor or was disposed of after a clearUndisposedObjects ' + + 'call'); + } + delete goog.Disposable.instances_[uid]; + } + } +}; + + +/** + * Associates a disposable object with this object so that they will be disposed + * together. + * @param {goog.disposable.IDisposable} disposable that will be disposed when + * this object is disposed. + */ +goog.Disposable.prototype.registerDisposable = function(disposable) { + this.addOnDisposeCallback(goog.partial(goog.dispose, disposable)); +}; + + +/** + * Invokes a callback function when this object is disposed. Callbacks are + * invoked in the order in which they were added. + * @param {function(this:T):?} callback The callback function. + * @param {T=} opt_scope An optional scope to call the callback in. + * @template T + */ +goog.Disposable.prototype.addOnDisposeCallback = function(callback, opt_scope) { + if (!this.onDisposeCallbacks_) { + this.onDisposeCallbacks_ = []; + } + + this.onDisposeCallbacks_.push( + goog.isDef(opt_scope) ? goog.bind(callback, opt_scope) : callback); +}; + + +/** + * Deletes or nulls out any references to COM objects, DOM nodes, or other + * disposable objects. Classes that extend {@code goog.Disposable} should + * override this method. + * Not reentrant. To avoid calling it twice, it must only be called from the + * subclass' {@code disposeInternal} method. Everywhere else the public + * {@code dispose} method must be used. + * For example: + * <pre> + * mypackage.MyClass = function() { + * mypackage.MyClass.base(this, 'constructor'); + * // Constructor logic specific to MyClass. + * ... + * }; + * goog.inherits(mypackage.MyClass, goog.Disposable); + * + * mypackage.MyClass.prototype.disposeInternal = function() { + * // Dispose logic specific to MyClass. + * ... + * // Call superclass's disposeInternal at the end of the subclass's, like + * // in C++, to avoid hard-to-catch issues. + * mypackage.MyClass.base(this, 'disposeInternal'); + * }; + * </pre> + * @protected + */ +goog.Disposable.prototype.disposeInternal = function() { + if (this.onDisposeCallbacks_) { + while (this.onDisposeCallbacks_.length) { + this.onDisposeCallbacks_.shift()(); + } + } +}; + + +/** + * Returns True if we can verify the object is disposed. + * Calls {@code isDisposed} on the argument if it supports it. If obj + * is not an object with an isDisposed() method, return false. + * @param {*} obj The object to investigate. + * @return {boolean} True if we can verify the object is disposed. + */ +goog.Disposable.isDisposed = function(obj) { + if (obj && typeof obj.isDisposed == 'function') { + return obj.isDisposed(); + } + return false; +}; + + +/** + * Calls {@code dispose} on the argument if it supports it. If obj is not an + * object with a dispose() method, this is a no-op. + * @param {*} obj The object to dispose of. + */ +goog.dispose = function(obj) { + if (obj && typeof obj.dispose == 'function') { + obj.dispose(); + } +}; + + +/** + * Calls {@code dispose} on each member of the list that supports it. (If the + * member is an ArrayLike, then {@code goog.disposeAll()} will be called + * recursively on each of its members.) If the member is not an object with a + * {@code dispose()} method, then it is ignored. + * @param {...*} var_args The list. + */ +goog.disposeAll = function(var_args) { + for (var i = 0, len = arguments.length; i < len; ++i) { + var disposable = arguments[i]; + if (goog.isArrayLike(disposable)) { + goog.disposeAll.apply(null, disposable); + } else { + goog.dispose(disposable); + } + } +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/disposable/idisposable.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/disposable/idisposable.js new file mode 100644 index 0000000..917d17e --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/disposable/idisposable.js @@ -0,0 +1,45 @@ +// Copyright 2011 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Definition of the disposable interface. A disposable object + * has a dispose method to to clean up references and resources. + * @author nnaze@google.com (Nathan Naze) + */ + + +goog.provide('goog.disposable.IDisposable'); + + + +/** + * Interface for a disposable object. If a instance requires cleanup + * (references COM objects, DOM notes, or other disposable objects), it should + * implement this interface (it may subclass goog.Disposable). + * @interface + */ +goog.disposable.IDisposable = function() {}; + + +/** + * Disposes of the object and its resources. + * @return {void} Nothing. + */ +goog.disposable.IDisposable.prototype.dispose = goog.abstractMethod; + + +/** + * @return {boolean} Whether the object has been disposed of. + */ +goog.disposable.IDisposable.prototype.isDisposed = goog.abstractMethod; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/browserfeature.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/browserfeature.js new file mode 100644 index 0000000..2c70cda --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/browserfeature.js @@ -0,0 +1,72 @@ +// Copyright 2010 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Browser capability checks for the dom package. + * + */ + + +goog.provide('goog.dom.BrowserFeature'); + +goog.require('goog.userAgent'); + + +/** + * Enum of browser capabilities. + * @enum {boolean} + */ +goog.dom.BrowserFeature = { + /** + * Whether attributes 'name' and 'type' can be added to an element after it's + * created. False in Internet Explorer prior to version 9. + */ + CAN_ADD_NAME_OR_TYPE_ATTRIBUTES: !goog.userAgent.IE || + goog.userAgent.isDocumentModeOrHigher(9), + + /** + * Whether we can use element.children to access an element's Element + * children. Available since Gecko 1.9.1, IE 9. (IE<9 also includes comment + * nodes in the collection.) + */ + CAN_USE_CHILDREN_ATTRIBUTE: !goog.userAgent.GECKO && !goog.userAgent.IE || + goog.userAgent.IE && goog.userAgent.isDocumentModeOrHigher(9) || + goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9.1'), + + /** + * Opera, Safari 3, and Internet Explorer 9 all support innerText but they + * include text nodes in script and style tags. Not document-mode-dependent. + */ + CAN_USE_INNER_TEXT: ( + goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9')), + + /** + * MSIE, Opera, and Safari>=4 support element.parentElement to access an + * element's parent if it is an Element. + */ + CAN_USE_PARENT_ELEMENT_PROPERTY: goog.userAgent.IE || goog.userAgent.OPERA || + goog.userAgent.WEBKIT, + + /** + * Whether NoScope elements need a scoped element written before them in + * innerHTML. + * MSDN: http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx#1 + */ + INNER_HTML_NEEDS_SCOPED_ELEMENT: goog.userAgent.IE, + + /** + * Whether we use legacy IE range API. + */ + LEGACY_IE_RANGES: goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/classes.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/classes.js new file mode 100644 index 0000000..cc8e794 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/classes.js @@ -0,0 +1,227 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utilities for adding, removing and setting classes. Prefer + * {@link goog.dom.classlist} over these utilities since goog.dom.classlist + * conforms closer to the semantics of Element.classList, is faster (uses + * native methods rather than parsing strings on every call) and compiles + * to smaller code as a result. + * + * Note: these utilities are meant to operate on HTMLElements and + * will not work on elements with differing interfaces (such as SVGElements). + * + */ + + +goog.provide('goog.dom.classes'); + +goog.require('goog.array'); + + +/** + * Sets the entire class name of an element. + * @param {Node} element DOM node to set class of. + * @param {string} className Class name(s) to apply to element. + */ +goog.dom.classes.set = function(element, className) { + element.className = className; +}; + + +/** + * Gets an array of class names on an element + * @param {Node} element DOM node to get class of. + * @return {!Array} Class names on {@code element}. Some browsers add extra + * properties to the array. Do not depend on any of these! + */ +goog.dom.classes.get = function(element) { + var className = element.className; + // Some types of elements don't have a className in IE (e.g. iframes). + // Furthermore, in Firefox, className is not a string when the element is + // an SVG element. + return goog.isString(className) && className.match(/\S+/g) || []; +}; + + +/** + * Adds a class or classes to an element. Does not add multiples of class names. + * @param {Node} element DOM node to add class to. + * @param {...string} var_args Class names to add. + * @return {boolean} Whether class was added (or all classes were added). + */ +goog.dom.classes.add = function(element, var_args) { + var classes = goog.dom.classes.get(element); + var args = goog.array.slice(arguments, 1); + var expectedCount = classes.length + args.length; + goog.dom.classes.add_(classes, args); + goog.dom.classes.set(element, classes.join(' ')); + return classes.length == expectedCount; +}; + + +/** + * Removes a class or classes from an element. + * @param {Node} element DOM node to remove class from. + * @param {...string} var_args Class name(s) to remove. + * @return {boolean} Whether all classes in {@code var_args} were found and + * removed. + */ +goog.dom.classes.remove = function(element, var_args) { + var classes = goog.dom.classes.get(element); + var args = goog.array.slice(arguments, 1); + var newClasses = goog.dom.classes.getDifference_(classes, args); + goog.dom.classes.set(element, newClasses.join(' ')); + return newClasses.length == classes.length - args.length; +}; + + +/** + * Helper method for {@link goog.dom.classes.add} and + * {@link goog.dom.classes.addRemove}. Adds one or more classes to the supplied + * classes array. + * @param {Array.<string>} classes All class names for the element, will be + * updated to have the classes supplied in {@code args} added. + * @param {Array.<string>} args Class names to add. + * @private + */ +goog.dom.classes.add_ = function(classes, args) { + for (var i = 0; i < args.length; i++) { + if (!goog.array.contains(classes, args[i])) { + classes.push(args[i]); + } + } +}; + + +/** + * Helper method for {@link goog.dom.classes.remove} and + * {@link goog.dom.classes.addRemove}. Calculates the difference of two arrays. + * @param {!Array.<string>} arr1 First array. + * @param {!Array.<string>} arr2 Second array. + * @return {!Array.<string>} The first array without the elements of the second + * array. + * @private + */ +goog.dom.classes.getDifference_ = function(arr1, arr2) { + return goog.array.filter(arr1, function(item) { + return !goog.array.contains(arr2, item); + }); +}; + + +/** + * Switches a class on an element from one to another without disturbing other + * classes. If the fromClass isn't removed, the toClass won't be added. + * @param {Node} element DOM node to swap classes on. + * @param {string} fromClass Class to remove. + * @param {string} toClass Class to add. + * @return {boolean} Whether classes were switched. + */ +goog.dom.classes.swap = function(element, fromClass, toClass) { + var classes = goog.dom.classes.get(element); + + var removed = false; + for (var i = 0; i < classes.length; i++) { + if (classes[i] == fromClass) { + goog.array.splice(classes, i--, 1); + removed = true; + } + } + + if (removed) { + classes.push(toClass); + goog.dom.classes.set(element, classes.join(' ')); + } + + return removed; +}; + + +/** + * Adds zero or more classes to an element and removes zero or more as a single + * operation. Unlike calling {@link goog.dom.classes.add} and + * {@link goog.dom.classes.remove} separately, this is more efficient as it only + * parses the class property once. + * + * If a class is in both the remove and add lists, it will be added. Thus, + * you can use this instead of {@link goog.dom.classes.swap} when you have + * more than two class names that you want to swap. + * + * @param {Node} element DOM node to swap classes on. + * @param {?(string|Array.<string>)} classesToRemove Class or classes to + * remove, if null no classes are removed. + * @param {?(string|Array.<string>)} classesToAdd Class or classes to add, if + * null no classes are added. + */ +goog.dom.classes.addRemove = function(element, classesToRemove, classesToAdd) { + var classes = goog.dom.classes.get(element); + if (goog.isString(classesToRemove)) { + goog.array.remove(classes, classesToRemove); + } else if (goog.isArray(classesToRemove)) { + classes = goog.dom.classes.getDifference_(classes, classesToRemove); + } + + if (goog.isString(classesToAdd) && + !goog.array.contains(classes, classesToAdd)) { + classes.push(classesToAdd); + } else if (goog.isArray(classesToAdd)) { + goog.dom.classes.add_(classes, classesToAdd); + } + + goog.dom.classes.set(element, classes.join(' ')); +}; + + +/** + * Returns true if an element has a class. + * @param {Node} element DOM node to test. + * @param {string} className Class name to test for. + * @return {boolean} Whether element has the class. + */ +goog.dom.classes.has = function(element, className) { + return goog.array.contains(goog.dom.classes.get(element), className); +}; + + +/** + * Adds or removes a class depending on the enabled argument. + * @param {Node} element DOM node to add or remove the class on. + * @param {string} className Class name to add or remove. + * @param {boolean} enabled Whether to add or remove the class (true adds, + * false removes). + */ +goog.dom.classes.enable = function(element, className, enabled) { + if (enabled) { + goog.dom.classes.add(element, className); + } else { + goog.dom.classes.remove(element, className); + } +}; + + +/** + * Removes a class if an element has it, and adds it the element doesn't have + * it. Won't affect other classes on the node. + * @param {Node} element DOM node to toggle class on. + * @param {string} className Class to toggle. + * @return {boolean} True if class was added, false if it was removed + * (in other words, whether element has the class after this function has + * been called). + */ +goog.dom.classes.toggle = function(element, className) { + var add = !goog.dom.classes.has(element, className); + goog.dom.classes.enable(element, className, add); + return add; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/classlist.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/classlist.js new file mode 100644 index 0000000..bca8e6b --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/classlist.js @@ -0,0 +1,277 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utilities for detecting, adding and removing classes. Prefer + * this over goog.dom.classes for new code since it attempts to use classList + * (DOMTokenList: http://dom.spec.whatwg.org/#domtokenlist) which is faster + * and requires less code. + * + * Note: these utilities are meant to operate on HTMLElements + * and may have unexpected behavior on elements with differing interfaces + * (such as SVGElements). + */ + + +goog.provide('goog.dom.classlist'); + +goog.require('goog.array'); + + +/** + * Override this define at build-time if you know your target supports it. + * @define {boolean} Whether to use the classList property (DOMTokenList). + */ +goog.define('goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST', false); + + +/** + * Gets an array-like object of class names on an element. + * @param {Element} element DOM node to get the classes of. + * @return {!goog.array.ArrayLike} Class names on {@code element}. + */ +goog.dom.classlist.get = function(element) { + if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { + return element.classList; + } + + var className = element.className; + // Some types of elements don't have a className in IE (e.g. iframes). + // Furthermore, in Firefox, className is not a string when the element is + // an SVG element. + return goog.isString(className) && className.match(/\S+/g) || []; +}; + + +/** + * Sets the entire class name of an element. + * @param {Element} element DOM node to set class of. + * @param {string} className Class name(s) to apply to element. + */ +goog.dom.classlist.set = function(element, className) { + element.className = className; +}; + + +/** + * Returns true if an element has a class. This method may throw a DOM + * exception for an invalid or empty class name if DOMTokenList is used. + * @param {Element} element DOM node to test. + * @param {string} className Class name to test for. + * @return {boolean} Whether element has the class. + */ +goog.dom.classlist.contains = function(element, className) { + if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { + return element.classList.contains(className); + } + return goog.array.contains(goog.dom.classlist.get(element), className); +}; + + +/** + * Adds a class to an element. Does not add multiples of class names. This + * method may throw a DOM exception for an invalid or empty class name if + * DOMTokenList is used. + * @param {Element} element DOM node to add class to. + * @param {string} className Class name to add. + */ +goog.dom.classlist.add = function(element, className) { + if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { + element.classList.add(className); + return; + } + + if (!goog.dom.classlist.contains(element, className)) { + // Ensure we add a space if this is not the first class name added. + element.className += element.className.length > 0 ? + (' ' + className) : className; + } +}; + + +/** + * Convenience method to add a number of class names at once. + * @param {Element} element The element to which to add classes. + * @param {goog.array.ArrayLike.<string>} classesToAdd An array-like object + * containing a collection of class names to add to the element. + * This method may throw a DOM exception if classesToAdd contains invalid + * or empty class names. + */ +goog.dom.classlist.addAll = function(element, classesToAdd) { + if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { + goog.array.forEach(classesToAdd, function(className) { + goog.dom.classlist.add(element, className); + }); + return; + } + + var classMap = {}; + + // Get all current class names into a map. + goog.array.forEach(goog.dom.classlist.get(element), + function(className) { + classMap[className] = true; + }); + + // Add new class names to the map. + goog.array.forEach(classesToAdd, + function(className) { + classMap[className] = true; + }); + + // Flatten the keys of the map into the className. + element.className = ''; + for (var className in classMap) { + element.className += element.className.length > 0 ? + (' ' + className) : className; + } +}; + + +/** + * Removes a class from an element. This method may throw a DOM exception + * for an invalid or empty class name if DOMTokenList is used. + * @param {Element} element DOM node to remove class from. + * @param {string} className Class name to remove. + */ +goog.dom.classlist.remove = function(element, className) { + if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { + element.classList.remove(className); + return; + } + + if (goog.dom.classlist.contains(element, className)) { + // Filter out the class name. + element.className = goog.array.filter( + goog.dom.classlist.get(element), + function(c) { + return c != className; + }).join(' '); + } +}; + + +/** + * Removes a set of classes from an element. Prefer this call to + * repeatedly calling {@code goog.dom.classlist.remove} if you want to remove + * a large set of class names at once. + * @param {Element} element The element from which to remove classes. + * @param {goog.array.ArrayLike.<string>} classesToRemove An array-like object + * containing a collection of class names to remove from the element. + * This method may throw a DOM exception if classesToRemove contains invalid + * or empty class names. + */ +goog.dom.classlist.removeAll = function(element, classesToRemove) { + if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { + goog.array.forEach(classesToRemove, function(className) { + goog.dom.classlist.remove(element, className); + }); + return; + } + // Filter out those classes in classesToRemove. + element.className = goog.array.filter( + goog.dom.classlist.get(element), + function(className) { + // If this class is not one we are trying to remove, + // add it to the array of new class names. + return !goog.array.contains(classesToRemove, className); + }).join(' '); +}; + + +/** + * Adds or removes a class depending on the enabled argument. This method + * may throw a DOM exception for an invalid or empty class name if DOMTokenList + * is used. + * @param {Element} element DOM node to add or remove the class on. + * @param {string} className Class name to add or remove. + * @param {boolean} enabled Whether to add or remove the class (true adds, + * false removes). + */ +goog.dom.classlist.enable = function(element, className, enabled) { + if (enabled) { + goog.dom.classlist.add(element, className); + } else { + goog.dom.classlist.remove(element, className); + } +}; + + +/** + * Adds or removes a set of classes depending on the enabled argument. This + * method may throw a DOM exception for an invalid or empty class name if + * DOMTokenList is used. + * @param {!Element} element DOM node to add or remove the class on. + * @param {goog.array.ArrayLike.<string>} classesToEnable An array-like object + * containing a collection of class names to add or remove from the element. + * @param {boolean} enabled Whether to add or remove the classes (true adds, + * false removes). + */ +goog.dom.classlist.enableAll = function(element, classesToEnable, enabled) { + var f = enabled ? goog.dom.classlist.addAll : + goog.dom.classlist.removeAll; + f(element, classesToEnable); +}; + + +/** + * Switches a class on an element from one to another without disturbing other + * classes. If the fromClass isn't removed, the toClass won't be added. This + * method may throw a DOM exception if the class names are empty or invalid. + * @param {Element} element DOM node to swap classes on. + * @param {string} fromClass Class to remove. + * @param {string} toClass Class to add. + * @return {boolean} Whether classes were switched. + */ +goog.dom.classlist.swap = function(element, fromClass, toClass) { + if (goog.dom.classlist.contains(element, fromClass)) { + goog.dom.classlist.remove(element, fromClass); + goog.dom.classlist.add(element, toClass); + return true; + } + return false; +}; + + +/** + * Removes a class if an element has it, and adds it the element doesn't have + * it. Won't affect other classes on the node. This method may throw a DOM + * exception if the class name is empty or invalid. + * @param {Element} element DOM node to toggle class on. + * @param {string} className Class to toggle. + * @return {boolean} True if class was added, false if it was removed + * (in other words, whether element has the class after this function has + * been called). + */ +goog.dom.classlist.toggle = function(element, className) { + var add = !goog.dom.classlist.contains(element, className); + goog.dom.classlist.enable(element, className, add); + return add; +}; + + +/** + * Adds and removes a class of an element. Unlike + * {@link goog.dom.classlist.swap}, this method adds the classToAdd regardless + * of whether the classToRemove was present and had been removed. This method + * may throw a DOM exception if the class names are empty or invalid. + * + * @param {Element} element DOM node to swap classes on. + * @param {string} classToRemove Class to remove. + * @param {string} classToAdd Class to add. + */ +goog.dom.classlist.addRemove = function(element, classToRemove, classToAdd) { + goog.dom.classlist.remove(element, classToRemove); + goog.dom.classlist.add(element, classToAdd); +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/dom.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/dom.js new file mode 100644 index 0000000..1cb0dca --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/dom.js @@ -0,0 +1,2917 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utilities for manipulating the browser's Document Object Model + * Inspiration taken *heavily* from mochikit (http://mochikit.com/). + * + * You can use {@link goog.dom.DomHelper} to create new dom helpers that refer + * to a different document object. This is useful if you are working with + * frames or multiple windows. + * + */ + + +// TODO(arv): Rename/refactor getTextContent and getRawTextContent. The problem +// is that getTextContent should mimic the DOM3 textContent. We should add a +// getInnerText (or getText) which tries to return the visible text, innerText. + + +goog.provide('goog.dom'); +goog.provide('goog.dom.Appendable'); +goog.provide('goog.dom.DomHelper'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom.BrowserFeature'); +goog.require('goog.dom.NodeType'); +goog.require('goog.dom.TagName'); +goog.require('goog.functions'); +goog.require('goog.math.Coordinate'); +goog.require('goog.math.Size'); +goog.require('goog.object'); +goog.require('goog.string'); +goog.require('goog.userAgent'); + + +/** + * @define {boolean} Whether we know at compile time that the browser is in + * quirks mode. + */ +goog.define('goog.dom.ASSUME_QUIRKS_MODE', false); + + +/** + * @define {boolean} Whether we know at compile time that the browser is in + * standards compliance mode. + */ +goog.define('goog.dom.ASSUME_STANDARDS_MODE', false); + + +/** + * Whether we know the compatibility mode at compile time. + * @type {boolean} + * @private + */ +goog.dom.COMPAT_MODE_KNOWN_ = + goog.dom.ASSUME_QUIRKS_MODE || goog.dom.ASSUME_STANDARDS_MODE; + + +/** + * Gets the DomHelper object for the document where the element resides. + * @param {(Node|Window)=} opt_element If present, gets the DomHelper for this + * element. + * @return {!goog.dom.DomHelper} The DomHelper. + */ +goog.dom.getDomHelper = function(opt_element) { + return opt_element ? + new goog.dom.DomHelper(goog.dom.getOwnerDocument(opt_element)) : + (goog.dom.defaultDomHelper_ || + (goog.dom.defaultDomHelper_ = new goog.dom.DomHelper())); +}; + + +/** + * Cached default DOM helper. + * @type {goog.dom.DomHelper} + * @private + */ +goog.dom.defaultDomHelper_; + + +/** + * Gets the document object being used by the dom library. + * @return {!Document} Document object. + */ +goog.dom.getDocument = function() { + return document; +}; + + +/** + * Gets an element from the current document by element id. + * + * If an Element is passed in, it is returned. + * + * @param {string|Element} element Element ID or a DOM node. + * @return {Element} The element with the given ID, or the node passed in. + */ +goog.dom.getElement = function(element) { + return goog.dom.getElementHelper_(document, element); +}; + + +/** + * Gets an element by id from the given document (if present). + * If an element is given, it is returned. + * @param {!Document} doc + * @param {string|Element} element Element ID or a DOM node. + * @return {Element} The resulting element. + * @private + */ +goog.dom.getElementHelper_ = function(doc, element) { + return goog.isString(element) ? + doc.getElementById(element) : + element; +}; + + +/** + * Gets an element by id, asserting that the element is found. + * + * This is used when an element is expected to exist, and should fail with + * an assertion error if it does not (if assertions are enabled). + * + * @param {string} id Element ID. + * @return {!Element} The element with the given ID, if it exists. + */ +goog.dom.getRequiredElement = function(id) { + return goog.dom.getRequiredElementHelper_(document, id); +}; + + +/** + * Helper function for getRequiredElementHelper functions, both static and + * on DomHelper. Asserts the element with the given id exists. + * @param {!Document} doc + * @param {string} id + * @return {!Element} The element with the given ID, if it exists. + * @private + */ +goog.dom.getRequiredElementHelper_ = function(doc, id) { + // To prevent users passing in Elements as is permitted in getElement(). + goog.asserts.assertString(id); + var element = goog.dom.getElementHelper_(doc, id); + element = goog.asserts.assertElement(element, + 'No element found with id: ' + id); + return element; +}; + + +/** + * Alias for getElement. + * @param {string|Element} element Element ID or a DOM node. + * @return {Element} The element with the given ID, or the node passed in. + * @deprecated Use {@link goog.dom.getElement} instead. + */ +goog.dom.$ = goog.dom.getElement; + + +/** + * Looks up elements by both tag and class name, using browser native functions + * ({@code querySelectorAll}, {@code getElementsByTagName} or + * {@code getElementsByClassName}) where possible. This function + * is a useful, if limited, way of collecting a list of DOM elements + * with certain characteristics. {@code goog.dom.query} offers a + * more powerful and general solution which allows matching on CSS3 + * selector expressions, but at increased cost in code size. If all you + * need is particular tags belonging to a single class, this function + * is fast and sleek. + * + * Note that tag names are case sensitive in the SVG namespace, and this + * function converts opt_tag to uppercase for comparisons. For queries in the + * SVG namespace you should use querySelector or querySelectorAll instead. + * https://bugzilla.mozilla.org/show_bug.cgi?id=963870 + * https://bugs.webkit.org/show_bug.cgi?id=83438 + * + * @see {goog.dom.query} + * + * @param {?string=} opt_tag Element tag name. + * @param {?string=} opt_class Optional class name. + * @param {(Document|Element)=} opt_el Optional element to look in. + * @return { {length: number} } Array-like list of elements (only a length + * property and numerical indices are guaranteed to exist). + */ +goog.dom.getElementsByTagNameAndClass = function(opt_tag, opt_class, opt_el) { + return goog.dom.getElementsByTagNameAndClass_(document, opt_tag, opt_class, + opt_el); +}; + + +/** + * Returns a static, array-like list of the elements with the provided + * className. + * @see {goog.dom.query} + * @param {string} className the name of the class to look for. + * @param {(Document|Element)=} opt_el Optional element to look in. + * @return { {length: number} } The items found with the class name provided. + */ +goog.dom.getElementsByClass = function(className, opt_el) { + var parent = opt_el || document; + if (goog.dom.canUseQuerySelector_(parent)) { + return parent.querySelectorAll('.' + className); + } + return goog.dom.getElementsByTagNameAndClass_( + document, '*', className, opt_el); +}; + + +/** + * Returns the first element with the provided className. + * @see {goog.dom.query} + * @param {string} className the name of the class to look for. + * @param {Element|Document=} opt_el Optional element to look in. + * @return {Element} The first item with the class name provided. + */ +goog.dom.getElementByClass = function(className, opt_el) { + var parent = opt_el || document; + var retVal = null; + if (goog.dom.canUseQuerySelector_(parent)) { + retVal = parent.querySelector('.' + className); + } else { + retVal = goog.dom.getElementsByTagNameAndClass_( + document, '*', className, opt_el)[0]; + } + return retVal || null; +}; + + +/** + * Ensures an element with the given className exists, and then returns the + * first element with the provided className. + * @see {goog.dom.query} + * @param {string} className the name of the class to look for. + * @param {!Element|!Document=} opt_root Optional element or document to look + * in. + * @return {!Element} The first item with the class name provided. + * @throws {goog.asserts.AssertionError} Thrown if no element is found. + */ +goog.dom.getRequiredElementByClass = function(className, opt_root) { + var retValue = goog.dom.getElementByClass(className, opt_root); + return goog.asserts.assert(retValue, + 'No element found with className: ' + className); +}; + + +/** + * Prefer the standardized (http://www.w3.org/TR/selectors-api/), native and + * fast W3C Selectors API. + * @param {!(Element|Document)} parent The parent document object. + * @return {boolean} whether or not we can use parent.querySelector* APIs. + * @private + */ +goog.dom.canUseQuerySelector_ = function(parent) { + return !!(parent.querySelectorAll && parent.querySelector); +}; + + +/** + * Helper for {@code getElementsByTagNameAndClass}. + * @param {!Document} doc The document to get the elements in. + * @param {?string=} opt_tag Element tag name. + * @param {?string=} opt_class Optional class name. + * @param {(Document|Element)=} opt_el Optional element to look in. + * @return { {length: number} } Array-like list of elements (only a length + * property and numerical indices are guaranteed to exist). + * @private + */ +goog.dom.getElementsByTagNameAndClass_ = function(doc, opt_tag, opt_class, + opt_el) { + var parent = opt_el || doc; + var tagName = (opt_tag && opt_tag != '*') ? opt_tag.toUpperCase() : ''; + + if (goog.dom.canUseQuerySelector_(parent) && + (tagName || opt_class)) { + var query = tagName + (opt_class ? '.' + opt_class : ''); + return parent.querySelectorAll(query); + } + + // Use the native getElementsByClassName if available, under the assumption + // that even when the tag name is specified, there will be fewer elements to + // filter through when going by class than by tag name + if (opt_class && parent.getElementsByClassName) { + var els = parent.getElementsByClassName(opt_class); + + if (tagName) { + var arrayLike = {}; + var len = 0; + + // Filter for specific tags if requested. + for (var i = 0, el; el = els[i]; i++) { + if (tagName == el.nodeName) { + arrayLike[len++] = el; + } + } + arrayLike.length = len; + + return arrayLike; + } else { + return els; + } + } + + var els = parent.getElementsByTagName(tagName || '*'); + + if (opt_class) { + var arrayLike = {}; + var len = 0; + for (var i = 0, el; el = els[i]; i++) { + var className = el.className; + // Check if className has a split function since SVG className does not. + if (typeof className.split == 'function' && + goog.array.contains(className.split(/\s+/), opt_class)) { + arrayLike[len++] = el; + } + } + arrayLike.length = len; + return arrayLike; + } else { + return els; + } +}; + + +/** + * Alias for {@code getElementsByTagNameAndClass}. + * @param {?string=} opt_tag Element tag name. + * @param {?string=} opt_class Optional class name. + * @param {Element=} opt_el Optional element to look in. + * @return { {length: number} } Array-like list of elements (only a length + * property and numerical indices are guaranteed to exist). + * @deprecated Use {@link goog.dom.getElementsByTagNameAndClass} instead. + */ +goog.dom.$$ = goog.dom.getElementsByTagNameAndClass; + + +/** + * Sets multiple properties on a node. + * @param {Element} element DOM node to set properties on. + * @param {Object} properties Hash of property:value pairs. + */ +goog.dom.setProperties = function(element, properties) { + goog.object.forEach(properties, function(val, key) { + if (key == 'style') { + element.style.cssText = val; + } else if (key == 'class') { + element.className = val; + } else if (key == 'for') { + element.htmlFor = val; + } else if (key in goog.dom.DIRECT_ATTRIBUTE_MAP_) { + element.setAttribute(goog.dom.DIRECT_ATTRIBUTE_MAP_[key], val); + } else if (goog.string.startsWith(key, 'aria-') || + goog.string.startsWith(key, 'data-')) { + element.setAttribute(key, val); + } else { + element[key] = val; + } + }); +}; + + +/** + * Map of attributes that should be set using + * element.setAttribute(key, val) instead of element[key] = val. Used + * by goog.dom.setProperties. + * + * @type {Object} + * @private + */ +goog.dom.DIRECT_ATTRIBUTE_MAP_ = { + 'cellpadding': 'cellPadding', + 'cellspacing': 'cellSpacing', + 'colspan': 'colSpan', + 'frameborder': 'frameBorder', + 'height': 'height', + 'maxlength': 'maxLength', + 'role': 'role', + 'rowspan': 'rowSpan', + 'type': 'type', + 'usemap': 'useMap', + 'valign': 'vAlign', + 'width': 'width' +}; + + +/** + * Gets the dimensions of the viewport. + * + * Gecko Standards mode: + * docEl.clientWidth Width of viewport excluding scrollbar. + * win.innerWidth Width of viewport including scrollbar. + * body.clientWidth Width of body element. + * + * docEl.clientHeight Height of viewport excluding scrollbar. + * win.innerHeight Height of viewport including scrollbar. + * body.clientHeight Height of document. + * + * Gecko Backwards compatible mode: + * docEl.clientWidth Width of viewport excluding scrollbar. + * win.innerWidth Width of viewport including scrollbar. + * body.clientWidth Width of viewport excluding scrollbar. + * + * docEl.clientHeight Height of document. + * win.innerHeight Height of viewport including scrollbar. + * body.clientHeight Height of viewport excluding scrollbar. + * + * IE6/7 Standards mode: + * docEl.clientWidth Width of viewport excluding scrollbar. + * win.innerWidth Undefined. + * body.clientWidth Width of body element. + * + * docEl.clientHeight Height of viewport excluding scrollbar. + * win.innerHeight Undefined. + * body.clientHeight Height of document element. + * + * IE5 + IE6/7 Backwards compatible mode: + * docEl.clientWidth 0. + * win.innerWidth Undefined. + * body.clientWidth Width of viewport excluding scrollbar. + * + * docEl.clientHeight 0. + * win.innerHeight Undefined. + * body.clientHeight Height of viewport excluding scrollbar. + * + * Opera 9 Standards and backwards compatible mode: + * docEl.clientWidth Width of viewport excluding scrollbar. + * win.innerWidth Width of viewport including scrollbar. + * body.clientWidth Width of viewport excluding scrollbar. + * + * docEl.clientHeight Height of document. + * win.innerHeight Height of viewport including scrollbar. + * body.clientHeight Height of viewport excluding scrollbar. + * + * WebKit: + * Safari 2 + * docEl.clientHeight Same as scrollHeight. + * docEl.clientWidth Same as innerWidth. + * win.innerWidth Width of viewport excluding scrollbar. + * win.innerHeight Height of the viewport including scrollbar. + * frame.innerHeight Height of the viewport exluding scrollbar. + * + * Safari 3 (tested in 522) + * + * docEl.clientWidth Width of viewport excluding scrollbar. + * docEl.clientHeight Height of viewport excluding scrollbar in strict mode. + * body.clientHeight Height of viewport excluding scrollbar in quirks mode. + * + * @param {Window=} opt_window Optional window element to test. + * @return {!goog.math.Size} Object with values 'width' and 'height'. + */ +goog.dom.getViewportSize = function(opt_window) { + // TODO(arv): This should not take an argument + return goog.dom.getViewportSize_(opt_window || window); +}; + + +/** + * Helper for {@code getViewportSize}. + * @param {Window} win The window to get the view port size for. + * @return {!goog.math.Size} Object with values 'width' and 'height'. + * @private + */ +goog.dom.getViewportSize_ = function(win) { + var doc = win.document; + var el = goog.dom.isCss1CompatMode_(doc) ? doc.documentElement : doc.body; + return new goog.math.Size(el.clientWidth, el.clientHeight); +}; + + +/** + * Calculates the height of the document. + * + * @return {number} The height of the current document. + */ +goog.dom.getDocumentHeight = function() { + return goog.dom.getDocumentHeight_(window); +}; + + +/** + * Calculates the height of the document of the given window. + * + * Function code copied from the opensocial gadget api: + * gadgets.window.adjustHeight(opt_height) + * + * @private + * @param {Window} win The window whose document height to retrieve. + * @return {number} The height of the document of the given window. + */ +goog.dom.getDocumentHeight_ = function(win) { + // NOTE(eae): This method will return the window size rather than the document + // size in webkit quirks mode. + var doc = win.document; + var height = 0; + + if (doc) { + // Calculating inner content height is hard and different between + // browsers rendering in Strict vs. Quirks mode. We use a combination of + // three properties within document.body and document.documentElement: + // - scrollHeight + // - offsetHeight + // - clientHeight + // These values differ significantly between browsers and rendering modes. + // But there are patterns. It just takes a lot of time and persistence + // to figure out. + + // If the window has no contents, it has no height. (In IE10, + // document.body & document.documentElement are null in an empty iFrame.) + var body = doc.body; + var docEl = doc.documentElement; + if (!body && !docEl) { + return 0; + } + + // Get the height of the viewport + var vh = goog.dom.getViewportSize_(win).height; + if (goog.dom.isCss1CompatMode_(doc) && docEl.scrollHeight) { + // In Strict mode: + // The inner content height is contained in either: + // document.documentElement.scrollHeight + // document.documentElement.offsetHeight + // Based on studying the values output by different browsers, + // use the value that's NOT equal to the viewport height found above. + height = docEl.scrollHeight != vh ? + docEl.scrollHeight : docEl.offsetHeight; + } else { + // In Quirks mode: + // documentElement.clientHeight is equal to documentElement.offsetHeight + // except in IE. In most browsers, document.documentElement can be used + // to calculate the inner content height. + // However, in other browsers (e.g. IE), document.body must be used + // instead. How do we know which one to use? + // If document.documentElement.clientHeight does NOT equal + // document.documentElement.offsetHeight, then use document.body. + var sh = docEl.scrollHeight; + var oh = docEl.offsetHeight; + if (docEl.clientHeight != oh) { + sh = body.scrollHeight; + oh = body.offsetHeight; + } + + // Detect whether the inner content height is bigger or smaller + // than the bounding box (viewport). If bigger, take the larger + // value. If smaller, take the smaller value. + if (sh > vh) { + // Content is larger + height = sh > oh ? sh : oh; + } else { + // Content is smaller + height = sh < oh ? sh : oh; + } + } + } + + return height; +}; + + +/** + * Gets the page scroll distance as a coordinate object. + * + * @param {Window=} opt_window Optional window element to test. + * @return {!goog.math.Coordinate} Object with values 'x' and 'y'. + * @deprecated Use {@link goog.dom.getDocumentScroll} instead. + */ +goog.dom.getPageScroll = function(opt_window) { + var win = opt_window || goog.global || window; + return goog.dom.getDomHelper(win.document).getDocumentScroll(); +}; + + +/** + * Gets the document scroll distance as a coordinate object. + * + * @return {!goog.math.Coordinate} Object with values 'x' and 'y'. + */ +goog.dom.getDocumentScroll = function() { + return goog.dom.getDocumentScroll_(document); +}; + + +/** + * Helper for {@code getDocumentScroll}. + * + * @param {!Document} doc The document to get the scroll for. + * @return {!goog.math.Coordinate} Object with values 'x' and 'y'. + * @private + */ +goog.dom.getDocumentScroll_ = function(doc) { + var el = goog.dom.getDocumentScrollElement_(doc); + var win = goog.dom.getWindow_(doc); + if (goog.userAgent.IE && goog.userAgent.isVersionOrHigher('10') && + win.pageYOffset != el.scrollTop) { + // The keyboard on IE10 touch devices shifts the page using the pageYOffset + // without modifying scrollTop. For this case, we want the body scroll + // offsets. + return new goog.math.Coordinate(el.scrollLeft, el.scrollTop); + } + return new goog.math.Coordinate(win.pageXOffset || el.scrollLeft, + win.pageYOffset || el.scrollTop); +}; + + +/** + * Gets the document scroll element. + * @return {!Element} Scrolling element. + */ +goog.dom.getDocumentScrollElement = function() { + return goog.dom.getDocumentScrollElement_(document); +}; + + +/** + * Helper for {@code getDocumentScrollElement}. + * @param {!Document} doc The document to get the scroll element for. + * @return {!Element} Scrolling element. + * @private + */ +goog.dom.getDocumentScrollElement_ = function(doc) { + // WebKit needs body.scrollLeft in both quirks mode and strict mode. We also + // default to the documentElement if the document does not have a body (e.g. + // a SVG document). + if (!goog.userAgent.WEBKIT && goog.dom.isCss1CompatMode_(doc)) { + return doc.documentElement; + } + return doc.body || doc.documentElement; +}; + + +/** + * Gets the window object associated with the given document. + * + * @param {Document=} opt_doc Document object to get window for. + * @return {!Window} The window associated with the given document. + */ +goog.dom.getWindow = function(opt_doc) { + // TODO(arv): This should not take an argument. + return opt_doc ? goog.dom.getWindow_(opt_doc) : window; +}; + + +/** + * Helper for {@code getWindow}. + * + * @param {!Document} doc Document object to get window for. + * @return {!Window} The window associated with the given document. + * @private + */ +goog.dom.getWindow_ = function(doc) { + return doc.parentWindow || doc.defaultView; +}; + + +/** + * Returns a dom node with a set of attributes. This function accepts varargs + * for subsequent nodes to be added. Subsequent nodes will be added to the + * first node as childNodes. + * + * So: + * <code>createDom('div', null, createDom('p'), createDom('p'));</code> + * would return a div with two child paragraphs + * + * @param {string} tagName Tag to create. + * @param {(Object|Array.<string>|string)=} opt_attributes If object, then a map + * of name-value pairs for attributes. If a string, then this is the + * className of the new element. If an array, the elements will be joined + * together as the className of the new element. + * @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or + * strings for text nodes. If one of the var_args is an array or NodeList,i + * its elements will be added as childNodes instead. + * @return {!Element} Reference to a DOM node. + */ +goog.dom.createDom = function(tagName, opt_attributes, var_args) { + return goog.dom.createDom_(document, arguments); +}; + + +/** + * Helper for {@code createDom}. + * @param {!Document} doc The document to create the DOM in. + * @param {!Arguments} args Argument object passed from the callers. See + * {@code goog.dom.createDom} for details. + * @return {!Element} Reference to a DOM node. + * @private + */ +goog.dom.createDom_ = function(doc, args) { + var tagName = args[0]; + var attributes = args[1]; + + // Internet Explorer is dumb: http://msdn.microsoft.com/workshop/author/ + // dhtml/reference/properties/name_2.asp + // Also does not allow setting of 'type' attribute on 'input' or 'button'. + if (!goog.dom.BrowserFeature.CAN_ADD_NAME_OR_TYPE_ATTRIBUTES && attributes && + (attributes.name || attributes.type)) { + var tagNameArr = ['<', tagName]; + if (attributes.name) { + tagNameArr.push(' name="', goog.string.htmlEscape(attributes.name), + '"'); + } + if (attributes.type) { + tagNameArr.push(' type="', goog.string.htmlEscape(attributes.type), + '"'); + + // Clone attributes map to remove 'type' without mutating the input. + var clone = {}; + goog.object.extend(clone, attributes); + + // JSCompiler can't see how goog.object.extend added this property, + // because it was essentially added by reflection. + // So it needs to be quoted. + delete clone['type']; + + attributes = clone; + } + tagNameArr.push('>'); + tagName = tagNameArr.join(''); + } + + var element = doc.createElement(tagName); + + if (attributes) { + if (goog.isString(attributes)) { + element.className = attributes; + } else if (goog.isArray(attributes)) { + element.className = attributes.join(' '); + } else { + goog.dom.setProperties(element, attributes); + } + } + + if (args.length > 2) { + goog.dom.append_(doc, element, args, 2); + } + + return element; +}; + + +/** + * Appends a node with text or other nodes. + * @param {!Document} doc The document to create new nodes in. + * @param {!Node} parent The node to append nodes to. + * @param {!Arguments} args The values to add. See {@code goog.dom.append}. + * @param {number} startIndex The index of the array to start from. + * @private + */ +goog.dom.append_ = function(doc, parent, args, startIndex) { + function childHandler(child) { + // TODO(user): More coercion, ala MochiKit? + if (child) { + parent.appendChild(goog.isString(child) ? + doc.createTextNode(child) : child); + } + } + + for (var i = startIndex; i < args.length; i++) { + var arg = args[i]; + // TODO(attila): Fix isArrayLike to return false for a text node. + if (goog.isArrayLike(arg) && !goog.dom.isNodeLike(arg)) { + // If the argument is a node list, not a real array, use a clone, + // because forEach can't be used to mutate a NodeList. + goog.array.forEach(goog.dom.isNodeList(arg) ? + goog.array.toArray(arg) : arg, + childHandler); + } else { + childHandler(arg); + } + } +}; + + +/** + * Alias for {@code createDom}. + * @param {string} tagName Tag to create. + * @param {(string|Object)=} opt_attributes If object, then a map of name-value + * pairs for attributes. If a string, then this is the className of the new + * element. + * @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or + * strings for text nodes. If one of the var_args is an array, its + * children will be added as childNodes instead. + * @return {!Element} Reference to a DOM node. + * @deprecated Use {@link goog.dom.createDom} instead. + */ +goog.dom.$dom = goog.dom.createDom; + + +/** + * Creates a new element. + * @param {string} name Tag name. + * @return {!Element} The new element. + */ +goog.dom.createElement = function(name) { + return document.createElement(name); +}; + + +/** + * Creates a new text node. + * @param {number|string} content Content. + * @return {!Text} The new text node. + */ +goog.dom.createTextNode = function(content) { + return document.createTextNode(String(content)); +}; + + +/** + * Create a table. + * @param {number} rows The number of rows in the table. Must be >= 1. + * @param {number} columns The number of columns in the table. Must be >= 1. + * @param {boolean=} opt_fillWithNbsp If true, fills table entries with nsbps. + * @return {!Element} The created table. + */ +goog.dom.createTable = function(rows, columns, opt_fillWithNbsp) { + return goog.dom.createTable_(document, rows, columns, !!opt_fillWithNbsp); +}; + + +/** + * Create a table. + * @param {!Document} doc Document object to use to create the table. + * @param {number} rows The number of rows in the table. Must be >= 1. + * @param {number} columns The number of columns in the table. Must be >= 1. + * @param {boolean} fillWithNbsp If true, fills table entries with nsbps. + * @return {!Element} The created table. + * @private + */ +goog.dom.createTable_ = function(doc, rows, columns, fillWithNbsp) { + var rowHtml = ['<tr>']; + for (var i = 0; i < columns; i++) { + rowHtml.push(fillWithNbsp ? '<td> </td>' : '<td></td>'); + } + rowHtml.push('</tr>'); + rowHtml = rowHtml.join(''); + var totalHtml = ['<table>']; + for (i = 0; i < rows; i++) { + totalHtml.push(rowHtml); + } + totalHtml.push('</table>'); + + var elem = doc.createElement(goog.dom.TagName.DIV); + elem.innerHTML = totalHtml.join(''); + return /** @type {!Element} */ (elem.removeChild(elem.firstChild)); +}; + + +/** + * Converts an HTML string into a document fragment. The string must be + * sanitized in order to avoid cross-site scripting. For example + * {@code goog.dom.htmlToDocumentFragment('<img src=x onerror=alert(0)>')} + * triggers an alert in all browsers, even if the returned document fragment + * is thrown away immediately. + * + * @param {string} htmlString The HTML string to convert. + * @return {!Node} The resulting document fragment. + */ +goog.dom.htmlToDocumentFragment = function(htmlString) { + return goog.dom.htmlToDocumentFragment_(document, htmlString); +}; + + +/** + * Helper for {@code htmlToDocumentFragment}. + * + * @param {!Document} doc The document. + * @param {string} htmlString The HTML string to convert. + * @return {!Node} The resulting document fragment. + * @private + */ +goog.dom.htmlToDocumentFragment_ = function(doc, htmlString) { + var tempDiv = doc.createElement('div'); + if (goog.dom.BrowserFeature.INNER_HTML_NEEDS_SCOPED_ELEMENT) { + tempDiv.innerHTML = '<br>' + htmlString; + tempDiv.removeChild(tempDiv.firstChild); + } else { + tempDiv.innerHTML = htmlString; + } + if (tempDiv.childNodes.length == 1) { + return /** @type {!Node} */ (tempDiv.removeChild(tempDiv.firstChild)); + } else { + var fragment = doc.createDocumentFragment(); + while (tempDiv.firstChild) { + fragment.appendChild(tempDiv.firstChild); + } + return fragment; + } +}; + + +/** + * Returns true if the browser is in "CSS1-compatible" (standards-compliant) + * mode, false otherwise. + * @return {boolean} True if in CSS1-compatible mode. + */ +goog.dom.isCss1CompatMode = function() { + return goog.dom.isCss1CompatMode_(document); +}; + + +/** + * Returns true if the browser is in "CSS1-compatible" (standards-compliant) + * mode, false otherwise. + * @param {Document} doc The document to check. + * @return {boolean} True if in CSS1-compatible mode. + * @private + */ +goog.dom.isCss1CompatMode_ = function(doc) { + if (goog.dom.COMPAT_MODE_KNOWN_) { + return goog.dom.ASSUME_STANDARDS_MODE; + } + + return doc.compatMode == 'CSS1Compat'; +}; + + +/** + * Determines if the given node can contain children, intended to be used for + * HTML generation. + * + * IE natively supports node.canHaveChildren but has inconsistent behavior. + * Prior to IE8 the base tag allows children and in IE9 all nodes return true + * for canHaveChildren. + * + * In practice all non-IE browsers allow you to add children to any node, but + * the behavior is inconsistent: + * + * <pre> + * var a = document.createElement('br'); + * a.appendChild(document.createTextNode('foo')); + * a.appendChild(document.createTextNode('bar')); + * console.log(a.childNodes.length); // 2 + * console.log(a.innerHTML); // Chrome: "", IE9: "foobar", FF3.5: "foobar" + * </pre> + * + * For more information, see: + * http://dev.w3.org/html5/markup/syntax.html#syntax-elements + * + * TODO(user): Rename shouldAllowChildren() ? + * + * @param {Node} node The node to check. + * @return {boolean} Whether the node can contain children. + */ +goog.dom.canHaveChildren = function(node) { + if (node.nodeType != goog.dom.NodeType.ELEMENT) { + return false; + } + switch (node.tagName) { + case goog.dom.TagName.APPLET: + case goog.dom.TagName.AREA: + case goog.dom.TagName.BASE: + case goog.dom.TagName.BR: + case goog.dom.TagName.COL: + case goog.dom.TagName.COMMAND: + case goog.dom.TagName.EMBED: + case goog.dom.TagName.FRAME: + case goog.dom.TagName.HR: + case goog.dom.TagName.IMG: + case goog.dom.TagName.INPUT: + case goog.dom.TagName.IFRAME: + case goog.dom.TagName.ISINDEX: + case goog.dom.TagName.KEYGEN: + case goog.dom.TagName.LINK: + case goog.dom.TagName.NOFRAMES: + case goog.dom.TagName.NOSCRIPT: + case goog.dom.TagName.META: + case goog.dom.TagName.OBJECT: + case goog.dom.TagName.PARAM: + case goog.dom.TagName.SCRIPT: + case goog.dom.TagName.SOURCE: + case goog.dom.TagName.STYLE: + case goog.dom.TagName.TRACK: + case goog.dom.TagName.WBR: + return false; + } + return true; +}; + + +/** + * Appends a child to a node. + * @param {Node} parent Parent. + * @param {Node} child Child. + */ +goog.dom.appendChild = function(parent, child) { + parent.appendChild(child); +}; + + +/** + * Appends a node with text or other nodes. + * @param {!Node} parent The node to append nodes to. + * @param {...goog.dom.Appendable} var_args The things to append to the node. + * If this is a Node it is appended as is. + * If this is a string then a text node is appended. + * If this is an array like object then fields 0 to length - 1 are appended. + */ +goog.dom.append = function(parent, var_args) { + goog.dom.append_(goog.dom.getOwnerDocument(parent), parent, arguments, 1); +}; + + +/** + * Removes all the child nodes on a DOM node. + * @param {Node} node Node to remove children from. + */ +goog.dom.removeChildren = function(node) { + // Note: Iterations over live collections can be slow, this is the fastest + // we could find. The double parenthesis are used to prevent JsCompiler and + // strict warnings. + var child; + while ((child = node.firstChild)) { + node.removeChild(child); + } +}; + + +/** + * Inserts a new node before an existing reference node (i.e. as the previous + * sibling). If the reference node has no parent, then does nothing. + * @param {Node} newNode Node to insert. + * @param {Node} refNode Reference node to insert before. + */ +goog.dom.insertSiblingBefore = function(newNode, refNode) { + if (refNode.parentNode) { + refNode.parentNode.insertBefore(newNode, refNode); + } +}; + + +/** + * Inserts a new node after an existing reference node (i.e. as the next + * sibling). If the reference node has no parent, then does nothing. + * @param {Node} newNode Node to insert. + * @param {Node} refNode Reference node to insert after. + */ +goog.dom.insertSiblingAfter = function(newNode, refNode) { + if (refNode.parentNode) { + refNode.parentNode.insertBefore(newNode, refNode.nextSibling); + } +}; + + +/** + * Insert a child at a given index. If index is larger than the number of child + * nodes that the parent currently has, the node is inserted as the last child + * node. + * @param {Element} parent The element into which to insert the child. + * @param {Node} child The element to insert. + * @param {number} index The index at which to insert the new child node. Must + * not be negative. + */ +goog.dom.insertChildAt = function(parent, child, index) { + // Note that if the second argument is null, insertBefore + // will append the child at the end of the list of children. + parent.insertBefore(child, parent.childNodes[index] || null); +}; + + +/** + * Removes a node from its parent. + * @param {Node} node The node to remove. + * @return {Node} The node removed if removed; else, null. + */ +goog.dom.removeNode = function(node) { + return node && node.parentNode ? node.parentNode.removeChild(node) : null; +}; + + +/** + * Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no + * parent. + * @param {Node} newNode Node to insert. + * @param {Node} oldNode Node to replace. + */ +goog.dom.replaceNode = function(newNode, oldNode) { + var parent = oldNode.parentNode; + if (parent) { + parent.replaceChild(newNode, oldNode); + } +}; + + +/** + * Flattens an element. That is, removes it and replace it with its children. + * Does nothing if the element is not in the document. + * @param {Element} element The element to flatten. + * @return {Element|undefined} The original element, detached from the document + * tree, sans children; or undefined, if the element was not in the document + * to begin with. + */ +goog.dom.flattenElement = function(element) { + var child, parent = element.parentNode; + if (parent && parent.nodeType != goog.dom.NodeType.DOCUMENT_FRAGMENT) { + // Use IE DOM method (supported by Opera too) if available + if (element.removeNode) { + return /** @type {Element} */ (element.removeNode(false)); + } else { + // Move all children of the original node up one level. + while ((child = element.firstChild)) { + parent.insertBefore(child, element); + } + + // Detach the original element. + return /** @type {Element} */ (goog.dom.removeNode(element)); + } + } +}; + + +/** + * Returns an array containing just the element children of the given element. + * @param {Element} element The element whose element children we want. + * @return {!(Array|NodeList)} An array or array-like list of just the element + * children of the given element. + */ +goog.dom.getChildren = function(element) { + // We check if the children attribute is supported for child elements + // since IE8 misuses the attribute by also including comments. + if (goog.dom.BrowserFeature.CAN_USE_CHILDREN_ATTRIBUTE && + element.children != undefined) { + return element.children; + } + // Fall back to manually filtering the element's child nodes. + return goog.array.filter(element.childNodes, function(node) { + return node.nodeType == goog.dom.NodeType.ELEMENT; + }); +}; + + +/** + * Returns the first child node that is an element. + * @param {Node} node The node to get the first child element of. + * @return {Element} The first child node of {@code node} that is an element. + */ +goog.dom.getFirstElementChild = function(node) { + if (node.firstElementChild != undefined) { + return /** @type {Element} */(node).firstElementChild; + } + return goog.dom.getNextElementNode_(node.firstChild, true); +}; + + +/** + * Returns the last child node that is an element. + * @param {Node} node The node to get the last child element of. + * @return {Element} The last child node of {@code node} that is an element. + */ +goog.dom.getLastElementChild = function(node) { + if (node.lastElementChild != undefined) { + return /** @type {Element} */(node).lastElementChild; + } + return goog.dom.getNextElementNode_(node.lastChild, false); +}; + + +/** + * Returns the first next sibling that is an element. + * @param {Node} node The node to get the next sibling element of. + * @return {Element} The next sibling of {@code node} that is an element. + */ +goog.dom.getNextElementSibling = function(node) { + if (node.nextElementSibling != undefined) { + return /** @type {Element} */(node).nextElementSibling; + } + return goog.dom.getNextElementNode_(node.nextSibling, true); +}; + + +/** + * Returns the first previous sibling that is an element. + * @param {Node} node The node to get the previous sibling element of. + * @return {Element} The first previous sibling of {@code node} that is + * an element. + */ +goog.dom.getPreviousElementSibling = function(node) { + if (node.previousElementSibling != undefined) { + return /** @type {Element} */(node).previousElementSibling; + } + return goog.dom.getNextElementNode_(node.previousSibling, false); +}; + + +/** + * Returns the first node that is an element in the specified direction, + * starting with {@code node}. + * @param {Node} node The node to get the next element from. + * @param {boolean} forward Whether to look forwards or backwards. + * @return {Element} The first element. + * @private + */ +goog.dom.getNextElementNode_ = function(node, forward) { + while (node && node.nodeType != goog.dom.NodeType.ELEMENT) { + node = forward ? node.nextSibling : node.previousSibling; + } + + return /** @type {Element} */ (node); +}; + + +/** + * Returns the next node in source order from the given node. + * @param {Node} node The node. + * @return {Node} The next node in the DOM tree, or null if this was the last + * node. + */ +goog.dom.getNextNode = function(node) { + if (!node) { + return null; + } + + if (node.firstChild) { + return node.firstChild; + } + + while (node && !node.nextSibling) { + node = node.parentNode; + } + + return node ? node.nextSibling : null; +}; + + +/** + * Returns the previous node in source order from the given node. + * @param {Node} node The node. + * @return {Node} The previous node in the DOM tree, or null if this was the + * first node. + */ +goog.dom.getPreviousNode = function(node) { + if (!node) { + return null; + } + + if (!node.previousSibling) { + return node.parentNode; + } + + node = node.previousSibling; + while (node && node.lastChild) { + node = node.lastChild; + } + + return node; +}; + + +/** + * Whether the object looks like a DOM node. + * @param {?} obj The object being tested for node likeness. + * @return {boolean} Whether the object looks like a DOM node. + */ +goog.dom.isNodeLike = function(obj) { + return goog.isObject(obj) && obj.nodeType > 0; +}; + + +/** + * Whether the object looks like an Element. + * @param {?} obj The object being tested for Element likeness. + * @return {boolean} Whether the object looks like an Element. + */ +goog.dom.isElement = function(obj) { + return goog.isObject(obj) && obj.nodeType == goog.dom.NodeType.ELEMENT; +}; + + +/** + * Returns true if the specified value is a Window object. This includes the + * global window for HTML pages, and iframe windows. + * @param {?} obj Variable to test. + * @return {boolean} Whether the variable is a window. + */ +goog.dom.isWindow = function(obj) { + return goog.isObject(obj) && obj['window'] == obj; +}; + + +/** + * Returns an element's parent, if it's an Element. + * @param {Element} element The DOM element. + * @return {Element} The parent, or null if not an Element. + */ +goog.dom.getParentElement = function(element) { + var parent; + if (goog.dom.BrowserFeature.CAN_USE_PARENT_ELEMENT_PROPERTY) { + var isIe9 = goog.userAgent.IE && + goog.userAgent.isVersionOrHigher('9') && + !goog.userAgent.isVersionOrHigher('10'); + // SVG elements in IE9 can't use the parentElement property. + // goog.global['SVGElement'] is not defined in IE9 quirks mode. + if (!(isIe9 && goog.global['SVGElement'] && + element instanceof goog.global['SVGElement'])) { + parent = element.parentElement; + if (parent) { + return parent; + } + } + } + parent = element.parentNode; + return goog.dom.isElement(parent) ? /** @type {!Element} */ (parent) : null; +}; + + +/** + * Whether a node contains another node. + * @param {Node} parent The node that should contain the other node. + * @param {Node} descendant The node to test presence of. + * @return {boolean} Whether the parent node contains the descendent node. + */ +goog.dom.contains = function(parent, descendant) { + // We use browser specific methods for this if available since it is faster + // that way. + + // IE DOM + if (parent.contains && descendant.nodeType == goog.dom.NodeType.ELEMENT) { + return parent == descendant || parent.contains(descendant); + } + + // W3C DOM Level 3 + if (typeof parent.compareDocumentPosition != 'undefined') { + return parent == descendant || + Boolean(parent.compareDocumentPosition(descendant) & 16); + } + + // W3C DOM Level 1 + while (descendant && parent != descendant) { + descendant = descendant.parentNode; + } + return descendant == parent; +}; + + +/** + * Compares the document order of two nodes, returning 0 if they are the same + * node, a negative number if node1 is before node2, and a positive number if + * node2 is before node1. Note that we compare the order the tags appear in the + * document so in the tree <b><i>text</i></b> the B node is considered to be + * before the I node. + * + * @param {Node} node1 The first node to compare. + * @param {Node} node2 The second node to compare. + * @return {number} 0 if the nodes are the same node, a negative number if node1 + * is before node2, and a positive number if node2 is before node1. + */ +goog.dom.compareNodeOrder = function(node1, node2) { + // Fall out quickly for equality. + if (node1 == node2) { + return 0; + } + + // Use compareDocumentPosition where available + if (node1.compareDocumentPosition) { + // 4 is the bitmask for FOLLOWS. + return node1.compareDocumentPosition(node2) & 2 ? 1 : -1; + } + + // Special case for document nodes on IE 7 and 8. + if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) { + if (node1.nodeType == goog.dom.NodeType.DOCUMENT) { + return -1; + } + if (node2.nodeType == goog.dom.NodeType.DOCUMENT) { + return 1; + } + } + + // Process in IE using sourceIndex - we check to see if the first node has + // a source index or if its parent has one. + if ('sourceIndex' in node1 || + (node1.parentNode && 'sourceIndex' in node1.parentNode)) { + var isElement1 = node1.nodeType == goog.dom.NodeType.ELEMENT; + var isElement2 = node2.nodeType == goog.dom.NodeType.ELEMENT; + + if (isElement1 && isElement2) { + return node1.sourceIndex - node2.sourceIndex; + } else { + var parent1 = node1.parentNode; + var parent2 = node2.parentNode; + + if (parent1 == parent2) { + return goog.dom.compareSiblingOrder_(node1, node2); + } + + if (!isElement1 && goog.dom.contains(parent1, node2)) { + return -1 * goog.dom.compareParentsDescendantNodeIe_(node1, node2); + } + + + if (!isElement2 && goog.dom.contains(parent2, node1)) { + return goog.dom.compareParentsDescendantNodeIe_(node2, node1); + } + + return (isElement1 ? node1.sourceIndex : parent1.sourceIndex) - + (isElement2 ? node2.sourceIndex : parent2.sourceIndex); + } + } + + // For Safari, we compare ranges. + var doc = goog.dom.getOwnerDocument(node1); + + var range1, range2; + range1 = doc.createRange(); + range1.selectNode(node1); + range1.collapse(true); + + range2 = doc.createRange(); + range2.selectNode(node2); + range2.collapse(true); + + return range1.compareBoundaryPoints(goog.global['Range'].START_TO_END, + range2); +}; + + +/** + * Utility function to compare the position of two nodes, when + * {@code textNode}'s parent is an ancestor of {@code node}. If this entry + * condition is not met, this function will attempt to reference a null object. + * @param {Node} textNode The textNode to compare. + * @param {Node} node The node to compare. + * @return {number} -1 if node is before textNode, +1 otherwise. + * @private + */ +goog.dom.compareParentsDescendantNodeIe_ = function(textNode, node) { + var parent = textNode.parentNode; + if (parent == node) { + // If textNode is a child of node, then node comes first. + return -1; + } + var sibling = node; + while (sibling.parentNode != parent) { + sibling = sibling.parentNode; + } + return goog.dom.compareSiblingOrder_(sibling, textNode); +}; + + +/** + * Utility function to compare the position of two nodes known to be non-equal + * siblings. + * @param {Node} node1 The first node to compare. + * @param {Node} node2 The second node to compare. + * @return {number} -1 if node1 is before node2, +1 otherwise. + * @private + */ +goog.dom.compareSiblingOrder_ = function(node1, node2) { + var s = node2; + while ((s = s.previousSibling)) { + if (s == node1) { + // We just found node1 before node2. + return -1; + } + } + + // Since we didn't find it, node1 must be after node2. + return 1; +}; + + +/** + * Find the deepest common ancestor of the given nodes. + * @param {...Node} var_args The nodes to find a common ancestor of. + * @return {Node} The common ancestor of the nodes, or null if there is none. + * null will only be returned if two or more of the nodes are from different + * documents. + */ +goog.dom.findCommonAncestor = function(var_args) { + var i, count = arguments.length; + if (!count) { + return null; + } else if (count == 1) { + return arguments[0]; + } + + var paths = []; + var minLength = Infinity; + for (i = 0; i < count; i++) { + // Compute the list of ancestors. + var ancestors = []; + var node = arguments[i]; + while (node) { + ancestors.unshift(node); + node = node.parentNode; + } + + // Save the list for comparison. + paths.push(ancestors); + minLength = Math.min(minLength, ancestors.length); + } + var output = null; + for (i = 0; i < minLength; i++) { + var first = paths[0][i]; + for (var j = 1; j < count; j++) { + if (first != paths[j][i]) { + return output; + } + } + output = first; + } + return output; +}; + + +/** + * Returns the owner document for a node. + * @param {Node|Window} node The node to get the document for. + * @return {!Document} The document owning the node. + */ +goog.dom.getOwnerDocument = function(node) { + // TODO(nnaze): Update param signature to be non-nullable. + goog.asserts.assert(node, 'Node cannot be null or undefined.'); + return /** @type {!Document} */ ( + node.nodeType == goog.dom.NodeType.DOCUMENT ? node : + node.ownerDocument || node.document); +}; + + +/** + * Cross-browser function for getting the document element of a frame or iframe. + * @param {Element} frame Frame element. + * @return {!Document} The frame content document. + */ +goog.dom.getFrameContentDocument = function(frame) { + var doc = frame.contentDocument || frame.contentWindow.document; + return doc; +}; + + +/** + * Cross-browser function for getting the window of a frame or iframe. + * @param {Element} frame Frame element. + * @return {Window} The window associated with the given frame. + */ +goog.dom.getFrameContentWindow = function(frame) { + return frame.contentWindow || + goog.dom.getWindow(goog.dom.getFrameContentDocument(frame)); +}; + + +/** + * Sets the text content of a node, with cross-browser support. + * @param {Node} node The node to change the text content of. + * @param {string|number} text The value that should replace the node's content. + */ +goog.dom.setTextContent = function(node, text) { + goog.asserts.assert(node != null, + 'goog.dom.setTextContent expects a non-null value for node'); + + if ('textContent' in node) { + node.textContent = text; + } else if (node.nodeType == goog.dom.NodeType.TEXT) { + node.data = text; + } else if (node.firstChild && + node.firstChild.nodeType == goog.dom.NodeType.TEXT) { + // If the first child is a text node we just change its data and remove the + // rest of the children. + while (node.lastChild != node.firstChild) { + node.removeChild(node.lastChild); + } + node.firstChild.data = text; + } else { + goog.dom.removeChildren(node); + var doc = goog.dom.getOwnerDocument(node); + node.appendChild(doc.createTextNode(String(text))); + } +}; + + +/** + * Gets the outerHTML of a node, which islike innerHTML, except that it + * actually contains the HTML of the node itself. + * @param {Element} element The element to get the HTML of. + * @return {string} The outerHTML of the given element. + */ +goog.dom.getOuterHtml = function(element) { + // IE, Opera and WebKit all have outerHTML. + if ('outerHTML' in element) { + return element.outerHTML; + } else { + var doc = goog.dom.getOwnerDocument(element); + var div = doc.createElement('div'); + div.appendChild(element.cloneNode(true)); + return div.innerHTML; + } +}; + + +/** + * Finds the first descendant node that matches the filter function, using + * a depth first search. This function offers the most general purpose way + * of finding a matching element. You may also wish to consider + * {@code goog.dom.query} which can express many matching criteria using + * CSS selector expressions. These expressions often result in a more + * compact representation of the desired result. + * @see goog.dom.query + * + * @param {Node} root The root of the tree to search. + * @param {function(Node) : boolean} p The filter function. + * @return {Node|undefined} The found node or undefined if none is found. + */ +goog.dom.findNode = function(root, p) { + var rv = []; + var found = goog.dom.findNodes_(root, p, rv, true); + return found ? rv[0] : undefined; +}; + + +/** + * Finds all the descendant nodes that match the filter function, using a + * a depth first search. This function offers the most general-purpose way + * of finding a set of matching elements. You may also wish to consider + * {@code goog.dom.query} which can express many matching criteria using + * CSS selector expressions. These expressions often result in a more + * compact representation of the desired result. + + * @param {Node} root The root of the tree to search. + * @param {function(Node) : boolean} p The filter function. + * @return {!Array.<!Node>} The found nodes or an empty array if none are found. + */ +goog.dom.findNodes = function(root, p) { + var rv = []; + goog.dom.findNodes_(root, p, rv, false); + return rv; +}; + + +/** + * Finds the first or all the descendant nodes that match the filter function, + * using a depth first search. + * @param {Node} root The root of the tree to search. + * @param {function(Node) : boolean} p The filter function. + * @param {!Array.<!Node>} rv The found nodes are added to this array. + * @param {boolean} findOne If true we exit after the first found node. + * @return {boolean} Whether the search is complete or not. True in case findOne + * is true and the node is found. False otherwise. + * @private + */ +goog.dom.findNodes_ = function(root, p, rv, findOne) { + if (root != null) { + var child = root.firstChild; + while (child) { + if (p(child)) { + rv.push(child); + if (findOne) { + return true; + } + } + if (goog.dom.findNodes_(child, p, rv, findOne)) { + return true; + } + child = child.nextSibling; + } + } + return false; +}; + + +/** + * Map of tags whose content to ignore when calculating text length. + * @type {Object} + * @private + */ +goog.dom.TAGS_TO_IGNORE_ = { + 'SCRIPT': 1, + 'STYLE': 1, + 'HEAD': 1, + 'IFRAME': 1, + 'OBJECT': 1 +}; + + +/** + * Map of tags which have predefined values with regard to whitespace. + * @type {Object} + * @private + */ +goog.dom.PREDEFINED_TAG_VALUES_ = {'IMG': ' ', 'BR': '\n'}; + + +/** + * Returns true if the element has a tab index that allows it to receive + * keyboard focus (tabIndex >= 0), false otherwise. Note that some elements + * natively support keyboard focus, even if they have no tab index. + * @param {Element} element Element to check. + * @return {boolean} Whether the element has a tab index that allows keyboard + * focus. + * @see http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + */ +goog.dom.isFocusableTabIndex = function(element) { + return goog.dom.hasSpecifiedTabIndex_(element) && + goog.dom.isTabIndexFocusable_(element); +}; + + +/** + * Enables or disables keyboard focus support on the element via its tab index. + * Only elements for which {@link goog.dom.isFocusableTabIndex} returns true + * (or elements that natively support keyboard focus, like form elements) can + * receive keyboard focus. See http://go/tabindex for more info. + * @param {Element} element Element whose tab index is to be changed. + * @param {boolean} enable Whether to set or remove a tab index on the element + * that supports keyboard focus. + */ +goog.dom.setFocusableTabIndex = function(element, enable) { + if (enable) { + element.tabIndex = 0; + } else { + // Set tabIndex to -1 first, then remove it. This is a workaround for + // Safari (confirmed in version 4 on Windows). When removing the attribute + // without setting it to -1 first, the element remains keyboard focusable + // despite not having a tabIndex attribute anymore. + element.tabIndex = -1; + element.removeAttribute('tabIndex'); // Must be camelCase! + } +}; + + +/** + * Returns true if the element can be focused, i.e. it has a tab index that + * allows it to receive keyboard focus (tabIndex >= 0), or it is an element + * that natively supports keyboard focus. + * @param {Element} element Element to check. + * @return {boolean} Whether the element allows keyboard focus. + */ +goog.dom.isFocusable = function(element) { + var focusable; + // Some elements can have unspecified tab index and still receive focus. + if (goog.dom.nativelySupportsFocus_(element)) { + // Make sure the element is not disabled ... + focusable = !element.disabled && + // ... and if a tab index is specified, it allows focus. + (!goog.dom.hasSpecifiedTabIndex_(element) || + goog.dom.isTabIndexFocusable_(element)); + } else { + focusable = goog.dom.isFocusableTabIndex(element); + } + + // IE requires elements to be visible in order to focus them. + return focusable && goog.userAgent.IE ? + goog.dom.hasNonZeroBoundingRect_(element) : focusable; +}; + + +/** + * Returns true if the element has a specified tab index. + * @param {Element} element Element to check. + * @return {boolean} Whether the element has a specified tab index. + * @private + */ +goog.dom.hasSpecifiedTabIndex_ = function(element) { + // IE returns 0 for an unset tabIndex, so we must use getAttributeNode(), + // which returns an object with a 'specified' property if tabIndex is + // specified. This works on other browsers, too. + var attrNode = element.getAttributeNode('tabindex'); // Must be lowercase! + return goog.isDefAndNotNull(attrNode) && attrNode.specified; +}; + + +/** + * Returns true if the element's tab index allows the element to be focused. + * @param {Element} element Element to check. + * @return {boolean} Whether the element's tab index allows focus. + * @private + */ +goog.dom.isTabIndexFocusable_ = function(element) { + var index = element.tabIndex; + // NOTE: IE9 puts tabIndex in 16-bit int, e.g. -2 is 65534. + return goog.isNumber(index) && index >= 0 && index < 32768; +}; + + +/** + * Returns true if the element is focusable even when tabIndex is not set. + * @param {Element} element Element to check. + * @return {boolean} Whether the element natively supports focus. + * @private + */ +goog.dom.nativelySupportsFocus_ = function(element) { + return element.tagName == goog.dom.TagName.A || + element.tagName == goog.dom.TagName.INPUT || + element.tagName == goog.dom.TagName.TEXTAREA || + element.tagName == goog.dom.TagName.SELECT || + element.tagName == goog.dom.TagName.BUTTON; +}; + + +/** + * Returns true if the element has a bounding rectangle that would be visible + * (i.e. its width and height are greater than zero). + * @param {Element} element Element to check. + * @return {boolean} Whether the element has a non-zero bounding rectangle. + * @private + */ +goog.dom.hasNonZeroBoundingRect_ = function(element) { + var rect = goog.isFunction(element['getBoundingClientRect']) ? + element.getBoundingClientRect() : + {'height': element.offsetHeight, 'width': element.offsetWidth}; + return goog.isDefAndNotNull(rect) && rect.height > 0 && rect.width > 0; +}; + + +/** + * Returns the text content of the current node, without markup and invisible + * symbols. New lines are stripped and whitespace is collapsed, + * such that each character would be visible. + * + * In browsers that support it, innerText is used. Other browsers attempt to + * simulate it via node traversal. Line breaks are canonicalized in IE. + * + * @param {Node} node The node from which we are getting content. + * @return {string} The text content. + */ +goog.dom.getTextContent = function(node) { + var textContent; + // Note(arv): IE9, Opera, and Safari 3 support innerText but they include + // text nodes in script tags. So we revert to use a user agent test here. + if (goog.dom.BrowserFeature.CAN_USE_INNER_TEXT && ('innerText' in node)) { + textContent = goog.string.canonicalizeNewlines(node.innerText); + // Unfortunately .innerText() returns text with ­ symbols + // We need to filter it out and then remove duplicate whitespaces + } else { + var buf = []; + goog.dom.getTextContent_(node, buf, true); + textContent = buf.join(''); + } + + // Strip ­ entities. goog.format.insertWordBreaks inserts them in Opera. + textContent = textContent.replace(/ \xAD /g, ' ').replace(/\xAD/g, ''); + // Strip ​ entities. goog.format.insertWordBreaks inserts them in IE8. + textContent = textContent.replace(/\u200B/g, ''); + + // Skip this replacement on old browsers with working innerText, which + // automatically turns into ' ' and / +/ into ' ' when reading + // innerText. + if (!goog.dom.BrowserFeature.CAN_USE_INNER_TEXT) { + textContent = textContent.replace(/ +/g, ' '); + } + if (textContent != ' ') { + textContent = textContent.replace(/^\s*/, ''); + } + + return textContent; +}; + + +/** + * Returns the text content of the current node, without markup. + * + * Unlike {@code getTextContent} this method does not collapse whitespaces + * or normalize lines breaks. + * + * @param {Node} node The node from which we are getting content. + * @return {string} The raw text content. + */ +goog.dom.getRawTextContent = function(node) { + var buf = []; + goog.dom.getTextContent_(node, buf, false); + + return buf.join(''); +}; + + +/** + * Recursive support function for text content retrieval. + * + * @param {Node} node The node from which we are getting content. + * @param {Array} buf string buffer. + * @param {boolean} normalizeWhitespace Whether to normalize whitespace. + * @private + */ +goog.dom.getTextContent_ = function(node, buf, normalizeWhitespace) { + if (node.nodeName in goog.dom.TAGS_TO_IGNORE_) { + // ignore certain tags + } else if (node.nodeType == goog.dom.NodeType.TEXT) { + if (normalizeWhitespace) { + buf.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, '')); + } else { + buf.push(node.nodeValue); + } + } else if (node.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) { + buf.push(goog.dom.PREDEFINED_TAG_VALUES_[node.nodeName]); + } else { + var child = node.firstChild; + while (child) { + goog.dom.getTextContent_(child, buf, normalizeWhitespace); + child = child.nextSibling; + } + } +}; + + +/** + * Returns the text length of the text contained in a node, without markup. This + * is equivalent to the selection length if the node was selected, or the number + * of cursor movements to traverse the node. Images & BRs take one space. New + * lines are ignored. + * + * @param {Node} node The node whose text content length is being calculated. + * @return {number} The length of {@code node}'s text content. + */ +goog.dom.getNodeTextLength = function(node) { + return goog.dom.getTextContent(node).length; +}; + + +/** + * Returns the text offset of a node relative to one of its ancestors. The text + * length is the same as the length calculated by goog.dom.getNodeTextLength. + * + * @param {Node} node The node whose offset is being calculated. + * @param {Node=} opt_offsetParent The node relative to which the offset will + * be calculated. Defaults to the node's owner document's body. + * @return {number} The text offset. + */ +goog.dom.getNodeTextOffset = function(node, opt_offsetParent) { + var root = opt_offsetParent || goog.dom.getOwnerDocument(node).body; + var buf = []; + while (node && node != root) { + var cur = node; + while ((cur = cur.previousSibling)) { + buf.unshift(goog.dom.getTextContent(cur)); + } + node = node.parentNode; + } + // Trim left to deal with FF cases when there might be line breaks and empty + // nodes at the front of the text + return goog.string.trimLeft(buf.join('')).replace(/ +/g, ' ').length; +}; + + +/** + * Returns the node at a given offset in a parent node. If an object is + * provided for the optional third parameter, the node and the remainder of the + * offset will stored as properties of this object. + * @param {Node} parent The parent node. + * @param {number} offset The offset into the parent node. + * @param {Object=} opt_result Object to be used to store the return value. The + * return value will be stored in the form {node: Node, remainder: number} + * if this object is provided. + * @return {Node} The node at the given offset. + */ +goog.dom.getNodeAtOffset = function(parent, offset, opt_result) { + var stack = [parent], pos = 0, cur = null; + while (stack.length > 0 && pos < offset) { + cur = stack.pop(); + if (cur.nodeName in goog.dom.TAGS_TO_IGNORE_) { + // ignore certain tags + } else if (cur.nodeType == goog.dom.NodeType.TEXT) { + var text = cur.nodeValue.replace(/(\r\n|\r|\n)/g, '').replace(/ +/g, ' '); + pos += text.length; + } else if (cur.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) { + pos += goog.dom.PREDEFINED_TAG_VALUES_[cur.nodeName].length; + } else { + for (var i = cur.childNodes.length - 1; i >= 0; i--) { + stack.push(cur.childNodes[i]); + } + } + } + if (goog.isObject(opt_result)) { + opt_result.remainder = cur ? cur.nodeValue.length + offset - pos - 1 : 0; + opt_result.node = cur; + } + + return cur; +}; + + +/** + * Returns true if the object is a {@code NodeList}. To qualify as a NodeList, + * the object must have a numeric length property and an item function (which + * has type 'string' on IE for some reason). + * @param {Object} val Object to test. + * @return {boolean} Whether the object is a NodeList. + */ +goog.dom.isNodeList = function(val) { + // TODO(attila): Now the isNodeList is part of goog.dom we can use + // goog.userAgent to make this simpler. + // A NodeList must have a length property of type 'number' on all platforms. + if (val && typeof val.length == 'number') { + // A NodeList is an object everywhere except Safari, where it's a function. + if (goog.isObject(val)) { + // A NodeList must have an item function (on non-IE platforms) or an item + // property of type 'string' (on IE). + return typeof val.item == 'function' || typeof val.item == 'string'; + } else if (goog.isFunction(val)) { + // On Safari, a NodeList is a function with an item property that is also + // a function. + return typeof val.item == 'function'; + } + } + + // Not a NodeList. + return false; +}; + + +/** + * Walks up the DOM hierarchy returning the first ancestor that has the passed + * tag name and/or class name. If the passed element matches the specified + * criteria, the element itself is returned. + * @param {Node} element The DOM node to start with. + * @param {?(goog.dom.TagName|string)=} opt_tag The tag name to match (or + * null/undefined to match only based on class name). + * @param {?string=} opt_class The class name to match (or null/undefined to + * match only based on tag name). + * @return {Element} The first ancestor that matches the passed criteria, or + * null if no match is found. + */ +goog.dom.getAncestorByTagNameAndClass = function(element, opt_tag, opt_class) { + if (!opt_tag && !opt_class) { + return null; + } + var tagName = opt_tag ? opt_tag.toUpperCase() : null; + return /** @type {Element} */ (goog.dom.getAncestor(element, + function(node) { + return (!tagName || node.nodeName == tagName) && + (!opt_class || goog.isString(node.className) && + goog.array.contains(node.className.split(/\s+/), opt_class)); + }, true)); +}; + + +/** + * Walks up the DOM hierarchy returning the first ancestor that has the passed + * class name. If the passed element matches the specified criteria, the + * element itself is returned. + * @param {Node} element The DOM node to start with. + * @param {string} className The class name to match. + * @return {Element} The first ancestor that matches the passed criteria, or + * null if none match. + */ +goog.dom.getAncestorByClass = function(element, className) { + return goog.dom.getAncestorByTagNameAndClass(element, null, className); +}; + + +/** + * Walks up the DOM hierarchy returning the first ancestor that passes the + * matcher function. + * @param {Node} element The DOM node to start with. + * @param {function(Node) : boolean} matcher A function that returns true if the + * passed node matches the desired criteria. + * @param {boolean=} opt_includeNode If true, the node itself is included in + * the search (the first call to the matcher will pass startElement as + * the node to test). + * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the + * dom. + * @return {Node} DOM node that matched the matcher, or null if there was + * no match. + */ +goog.dom.getAncestor = function( + element, matcher, opt_includeNode, opt_maxSearchSteps) { + if (!opt_includeNode) { + element = element.parentNode; + } + var ignoreSearchSteps = opt_maxSearchSteps == null; + var steps = 0; + while (element && (ignoreSearchSteps || steps <= opt_maxSearchSteps)) { + if (matcher(element)) { + return element; + } + element = element.parentNode; + steps++; + } + // Reached the root of the DOM without a match + return null; +}; + + +/** + * Determines the active element in the given document. + * @param {Document} doc The document to look in. + * @return {Element} The active element. + */ +goog.dom.getActiveElement = function(doc) { + try { + return doc && doc.activeElement; + } catch (e) { + // NOTE(nicksantos): Sometimes, evaluating document.activeElement in IE + // throws an exception. I'm not 100% sure why, but I suspect it chokes + // on document.activeElement if the activeElement has been recently + // removed from the DOM by a JS operation. + // + // We assume that an exception here simply means + // "there is no active element." + } + + return null; +}; + + +/** + * @private {number} Cached version of the devicePixelRatio. + */ +goog.dom.devicePixelRatio_; + + +/** + * Gives the devicePixelRatio, or attempts to determine if not present. + * + * By default, this is the same value given by window.devicePixelRatio. If + * devicePixelRatio is not defined, the ratio is calculated with + * window.matchMedia, if present. Otherwise, gives 1.0. + * + * This function is cached so that the pixel ratio is calculated only once + * and only calculated when first requested. + * + * @return {number} The number of actual pixels per virtual pixel. + */ +goog.dom.getPixelRatio = goog.functions.cacheReturnValue(function() { + var win = goog.dom.getWindow(); + + // devicePixelRatio does not work on Mobile firefox. + // TODO(user): Enable this check on a known working mobile Gecko version. + // Filed a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=896804 + var isFirefoxMobile = goog.userAgent.GECKO && goog.userAgent.MOBILE; + + if (goog.isDef(win.devicePixelRatio) && !isFirefoxMobile) { + return win.devicePixelRatio; + } else if (win.matchMedia) { + return goog.dom.matchesPixelRatio_(.75) || + goog.dom.matchesPixelRatio_(1.5) || + goog.dom.matchesPixelRatio_(2) || + goog.dom.matchesPixelRatio_(3) || 1; + } + return 1; +}); + + +/** + * Calculates a mediaQuery to check if the current device supports the + * given actual to virtual pixel ratio. + * @param {number} pixelRatio The ratio of actual pixels to virtual pixels. + * @return {number} pixelRatio if applicable, otherwise 0. + * @private + */ +goog.dom.matchesPixelRatio_ = function(pixelRatio) { + var win = goog.dom.getWindow(); + var query = ('(-webkit-min-device-pixel-ratio: ' + pixelRatio + '),' + + '(min--moz-device-pixel-ratio: ' + pixelRatio + '),' + + '(min-resolution: ' + pixelRatio + 'dppx)'); + return win.matchMedia(query).matches ? pixelRatio : 0; +}; + + + +/** + * Create an instance of a DOM helper with a new document object. + * @param {Document=} opt_document Document object to associate with this + * DOM helper. + * @constructor + */ +goog.dom.DomHelper = function(opt_document) { + /** + * Reference to the document object to use + * @type {!Document} + * @private + */ + this.document_ = opt_document || goog.global.document || document; +}; + + +/** + * Gets the dom helper object for the document where the element resides. + * @param {Node=} opt_node If present, gets the DomHelper for this node. + * @return {!goog.dom.DomHelper} The DomHelper. + */ +goog.dom.DomHelper.prototype.getDomHelper = goog.dom.getDomHelper; + + +/** + * Sets the document object. + * @param {!Document} document Document object. + */ +goog.dom.DomHelper.prototype.setDocument = function(document) { + this.document_ = document; +}; + + +/** + * Gets the document object being used by the dom library. + * @return {!Document} Document object. + */ +goog.dom.DomHelper.prototype.getDocument = function() { + return this.document_; +}; + + +/** + * Alias for {@code getElementById}. If a DOM node is passed in then we just + * return that. + * @param {string|Element} element Element ID or a DOM node. + * @return {Element} The element with the given ID, or the node passed in. + */ +goog.dom.DomHelper.prototype.getElement = function(element) { + return goog.dom.getElementHelper_(this.document_, element); +}; + + +/** + * Gets an element by id, asserting that the element is found. + * + * This is used when an element is expected to exist, and should fail with + * an assertion error if it does not (if assertions are enabled). + * + * @param {string} id Element ID. + * @return {!Element} The element with the given ID, if it exists. + */ +goog.dom.DomHelper.prototype.getRequiredElement = function(id) { + return goog.dom.getRequiredElementHelper_(this.document_, id); +}; + + +/** + * Alias for {@code getElement}. + * @param {string|Element} element Element ID or a DOM node. + * @return {Element} The element with the given ID, or the node passed in. + * @deprecated Use {@link goog.dom.DomHelper.prototype.getElement} instead. + */ +goog.dom.DomHelper.prototype.$ = goog.dom.DomHelper.prototype.getElement; + + +/** + * Looks up elements by both tag and class name, using browser native functions + * ({@code querySelectorAll}, {@code getElementsByTagName} or + * {@code getElementsByClassName}) where possible. The returned array is a live + * NodeList or a static list depending on the code path taken. + * + * @see goog.dom.query + * + * @param {?string=} opt_tag Element tag name or * for all tags. + * @param {?string=} opt_class Optional class name. + * @param {(Document|Element)=} opt_el Optional element to look in. + * @return { {length: number} } Array-like list of elements (only a length + * property and numerical indices are guaranteed to exist). + */ +goog.dom.DomHelper.prototype.getElementsByTagNameAndClass = function(opt_tag, + opt_class, + opt_el) { + return goog.dom.getElementsByTagNameAndClass_(this.document_, opt_tag, + opt_class, opt_el); +}; + + +/** + * Returns an array of all the elements with the provided className. + * @see {goog.dom.query} + * @param {string} className the name of the class to look for. + * @param {Element|Document=} opt_el Optional element to look in. + * @return { {length: number} } The items found with the class name provided. + */ +goog.dom.DomHelper.prototype.getElementsByClass = function(className, opt_el) { + var doc = opt_el || this.document_; + return goog.dom.getElementsByClass(className, doc); +}; + + +/** + * Returns the first element we find matching the provided class name. + * @see {goog.dom.query} + * @param {string} className the name of the class to look for. + * @param {(Element|Document)=} opt_el Optional element to look in. + * @return {Element} The first item found with the class name provided. + */ +goog.dom.DomHelper.prototype.getElementByClass = function(className, opt_el) { + var doc = opt_el || this.document_; + return goog.dom.getElementByClass(className, doc); +}; + + +/** + * Ensures an element with the given className exists, and then returns the + * first element with the provided className. + * @see {goog.dom.query} + * @param {string} className the name of the class to look for. + * @param {(!Element|!Document)=} opt_root Optional element or document to look + * in. + * @return {!Element} The first item found with the class name provided. + * @throws {goog.asserts.AssertionError} Thrown if no element is found. + */ +goog.dom.DomHelper.prototype.getRequiredElementByClass = function(className, + opt_root) { + var root = opt_root || this.document_; + return goog.dom.getRequiredElementByClass(className, root); +}; + + +/** + * Alias for {@code getElementsByTagNameAndClass}. + * @deprecated Use DomHelper getElementsByTagNameAndClass. + * @see goog.dom.query + * + * @param {?string=} opt_tag Element tag name. + * @param {?string=} opt_class Optional class name. + * @param {Element=} opt_el Optional element to look in. + * @return { {length: number} } Array-like list of elements (only a length + * property and numerical indices are guaranteed to exist). + */ +goog.dom.DomHelper.prototype.$$ = + goog.dom.DomHelper.prototype.getElementsByTagNameAndClass; + + +/** + * Sets a number of properties on a node. + * @param {Element} element DOM node to set properties on. + * @param {Object} properties Hash of property:value pairs. + */ +goog.dom.DomHelper.prototype.setProperties = goog.dom.setProperties; + + +/** + * Gets the dimensions of the viewport. + * @param {Window=} opt_window Optional window element to test. Defaults to + * the window of the Dom Helper. + * @return {!goog.math.Size} Object with values 'width' and 'height'. + */ +goog.dom.DomHelper.prototype.getViewportSize = function(opt_window) { + // TODO(arv): This should not take an argument. That breaks the rule of a + // a DomHelper representing a single frame/window/document. + return goog.dom.getViewportSize(opt_window || this.getWindow()); +}; + + +/** + * Calculates the height of the document. + * + * @return {number} The height of the document. + */ +goog.dom.DomHelper.prototype.getDocumentHeight = function() { + return goog.dom.getDocumentHeight_(this.getWindow()); +}; + + +/** + * Typedef for use with goog.dom.createDom and goog.dom.append. + * @typedef {Object|string|Array|NodeList} + */ +goog.dom.Appendable; + + +/** + * Returns a dom node with a set of attributes. This function accepts varargs + * for subsequent nodes to be added. Subsequent nodes will be added to the + * first node as childNodes. + * + * So: + * <code>createDom('div', null, createDom('p'), createDom('p'));</code> + * would return a div with two child paragraphs + * + * An easy way to move all child nodes of an existing element to a new parent + * element is: + * <code>createDom('div', null, oldElement.childNodes);</code> + * which will remove all child nodes from the old element and add them as + * child nodes of the new DIV. + * + * @param {string} tagName Tag to create. + * @param {Object|string=} opt_attributes If object, then a map of name-value + * pairs for attributes. If a string, then this is the className of the new + * element. + * @param {...goog.dom.Appendable} var_args Further DOM nodes or + * strings for text nodes. If one of the var_args is an array or + * NodeList, its elements will be added as childNodes instead. + * @return {!Element} Reference to a DOM node. + */ +goog.dom.DomHelper.prototype.createDom = function(tagName, + opt_attributes, + var_args) { + return goog.dom.createDom_(this.document_, arguments); +}; + + +/** + * Alias for {@code createDom}. + * @param {string} tagName Tag to create. + * @param {(Object|string)=} opt_attributes If object, then a map of name-value + * pairs for attributes. If a string, then this is the className of the new + * element. + * @param {...goog.dom.Appendable} var_args Further DOM nodes or strings for + * text nodes. If one of the var_args is an array, its children will be + * added as childNodes instead. + * @return {!Element} Reference to a DOM node. + * @deprecated Use {@link goog.dom.DomHelper.prototype.createDom} instead. + */ +goog.dom.DomHelper.prototype.$dom = goog.dom.DomHelper.prototype.createDom; + + +/** + * Creates a new element. + * @param {string} name Tag name. + * @return {!Element} The new element. + */ +goog.dom.DomHelper.prototype.createElement = function(name) { + return this.document_.createElement(name); +}; + + +/** + * Creates a new text node. + * @param {number|string} content Content. + * @return {!Text} The new text node. + */ +goog.dom.DomHelper.prototype.createTextNode = function(content) { + return this.document_.createTextNode(String(content)); +}; + + +/** + * Create a table. + * @param {number} rows The number of rows in the table. Must be >= 1. + * @param {number} columns The number of columns in the table. Must be >= 1. + * @param {boolean=} opt_fillWithNbsp If true, fills table entries with nsbps. + * @return {!Element} The created table. + */ +goog.dom.DomHelper.prototype.createTable = function(rows, columns, + opt_fillWithNbsp) { + return goog.dom.createTable_(this.document_, rows, columns, + !!opt_fillWithNbsp); +}; + + +/** + * Converts an HTML string into a node or a document fragment. A single Node + * is used if the {@code htmlString} only generates a single node. If the + * {@code htmlString} generates multiple nodes then these are put inside a + * {@code DocumentFragment}. + * + * @param {string} htmlString The HTML string to convert. + * @return {!Node} The resulting node. + */ +goog.dom.DomHelper.prototype.htmlToDocumentFragment = function(htmlString) { + return goog.dom.htmlToDocumentFragment_(this.document_, htmlString); +}; + + +/** + * Returns true if the browser is in "CSS1-compatible" (standards-compliant) + * mode, false otherwise. + * @return {boolean} True if in CSS1-compatible mode. + */ +goog.dom.DomHelper.prototype.isCss1CompatMode = function() { + return goog.dom.isCss1CompatMode_(this.document_); +}; + + +/** + * Gets the window object associated with the document. + * @return {!Window} The window associated with the given document. + */ +goog.dom.DomHelper.prototype.getWindow = function() { + return goog.dom.getWindow_(this.document_); +}; + + +/** + * Gets the document scroll element. + * @return {!Element} Scrolling element. + */ +goog.dom.DomHelper.prototype.getDocumentScrollElement = function() { + return goog.dom.getDocumentScrollElement_(this.document_); +}; + + +/** + * Gets the document scroll distance as a coordinate object. + * @return {!goog.math.Coordinate} Object with properties 'x' and 'y'. + */ +goog.dom.DomHelper.prototype.getDocumentScroll = function() { + return goog.dom.getDocumentScroll_(this.document_); +}; + + +/** + * Determines the active element in the given document. + * @param {Document=} opt_doc The document to look in. + * @return {Element} The active element. + */ +goog.dom.DomHelper.prototype.getActiveElement = function(opt_doc) { + return goog.dom.getActiveElement(opt_doc || this.document_); +}; + + +/** + * Appends a child to a node. + * @param {Node} parent Parent. + * @param {Node} child Child. + */ +goog.dom.DomHelper.prototype.appendChild = goog.dom.appendChild; + + +/** + * Appends a node with text or other nodes. + * @param {!Node} parent The node to append nodes to. + * @param {...goog.dom.Appendable} var_args The things to append to the node. + * If this is a Node it is appended as is. + * If this is a string then a text node is appended. + * If this is an array like object then fields 0 to length - 1 are appended. + */ +goog.dom.DomHelper.prototype.append = goog.dom.append; + + +/** + * Determines if the given node can contain children, intended to be used for + * HTML generation. + * + * @param {Node} node The node to check. + * @return {boolean} Whether the node can contain children. + */ +goog.dom.DomHelper.prototype.canHaveChildren = goog.dom.canHaveChildren; + + +/** + * Removes all the child nodes on a DOM node. + * @param {Node} node Node to remove children from. + */ +goog.dom.DomHelper.prototype.removeChildren = goog.dom.removeChildren; + + +/** + * Inserts a new node before an existing reference node (i.e., as the previous + * sibling). If the reference node has no parent, then does nothing. + * @param {Node} newNode Node to insert. + * @param {Node} refNode Reference node to insert before. + */ +goog.dom.DomHelper.prototype.insertSiblingBefore = goog.dom.insertSiblingBefore; + + +/** + * Inserts a new node after an existing reference node (i.e., as the next + * sibling). If the reference node has no parent, then does nothing. + * @param {Node} newNode Node to insert. + * @param {Node} refNode Reference node to insert after. + */ +goog.dom.DomHelper.prototype.insertSiblingAfter = goog.dom.insertSiblingAfter; + + +/** + * Insert a child at a given index. If index is larger than the number of child + * nodes that the parent currently has, the node is inserted as the last child + * node. + * @param {Element} parent The element into which to insert the child. + * @param {Node} child The element to insert. + * @param {number} index The index at which to insert the new child node. Must + * not be negative. + */ +goog.dom.DomHelper.prototype.insertChildAt = goog.dom.insertChildAt; + + +/** + * Removes a node from its parent. + * @param {Node} node The node to remove. + * @return {Node} The node removed if removed; else, null. + */ +goog.dom.DomHelper.prototype.removeNode = goog.dom.removeNode; + + +/** + * Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no + * parent. + * @param {Node} newNode Node to insert. + * @param {Node} oldNode Node to replace. + */ +goog.dom.DomHelper.prototype.replaceNode = goog.dom.replaceNode; + + +/** + * Flattens an element. That is, removes it and replace it with its children. + * @param {Element} element The element to flatten. + * @return {Element|undefined} The original element, detached from the document + * tree, sans children, or undefined if the element was already not in the + * document. + */ +goog.dom.DomHelper.prototype.flattenElement = goog.dom.flattenElement; + + +/** + * Returns an array containing just the element children of the given element. + * @param {Element} element The element whose element children we want. + * @return {!(Array|NodeList)} An array or array-like list of just the element + * children of the given element. + */ +goog.dom.DomHelper.prototype.getChildren = goog.dom.getChildren; + + +/** + * Returns the first child node that is an element. + * @param {Node} node The node to get the first child element of. + * @return {Element} The first child node of {@code node} that is an element. + */ +goog.dom.DomHelper.prototype.getFirstElementChild = + goog.dom.getFirstElementChild; + + +/** + * Returns the last child node that is an element. + * @param {Node} node The node to get the last child element of. + * @return {Element} The last child node of {@code node} that is an element. + */ +goog.dom.DomHelper.prototype.getLastElementChild = goog.dom.getLastElementChild; + + +/** + * Returns the first next sibling that is an element. + * @param {Node} node The node to get the next sibling element of. + * @return {Element} The next sibling of {@code node} that is an element. + */ +goog.dom.DomHelper.prototype.getNextElementSibling = + goog.dom.getNextElementSibling; + + +/** + * Returns the first previous sibling that is an element. + * @param {Node} node The node to get the previous sibling element of. + * @return {Element} The first previous sibling of {@code node} that is + * an element. + */ +goog.dom.DomHelper.prototype.getPreviousElementSibling = + goog.dom.getPreviousElementSibling; + + +/** + * Returns the next node in source order from the given node. + * @param {Node} node The node. + * @return {Node} The next node in the DOM tree, or null if this was the last + * node. + */ +goog.dom.DomHelper.prototype.getNextNode = goog.dom.getNextNode; + + +/** + * Returns the previous node in source order from the given node. + * @param {Node} node The node. + * @return {Node} The previous node in the DOM tree, or null if this was the + * first node. + */ +goog.dom.DomHelper.prototype.getPreviousNode = goog.dom.getPreviousNode; + + +/** + * Whether the object looks like a DOM node. + * @param {?} obj The object being tested for node likeness. + * @return {boolean} Whether the object looks like a DOM node. + */ +goog.dom.DomHelper.prototype.isNodeLike = goog.dom.isNodeLike; + + +/** + * Whether the object looks like an Element. + * @param {?} obj The object being tested for Element likeness. + * @return {boolean} Whether the object looks like an Element. + */ +goog.dom.DomHelper.prototype.isElement = goog.dom.isElement; + + +/** + * Returns true if the specified value is a Window object. This includes the + * global window for HTML pages, and iframe windows. + * @param {?} obj Variable to test. + * @return {boolean} Whether the variable is a window. + */ +goog.dom.DomHelper.prototype.isWindow = goog.dom.isWindow; + + +/** + * Returns an element's parent, if it's an Element. + * @param {Element} element The DOM element. + * @return {Element} The parent, or null if not an Element. + */ +goog.dom.DomHelper.prototype.getParentElement = goog.dom.getParentElement; + + +/** + * Whether a node contains another node. + * @param {Node} parent The node that should contain the other node. + * @param {Node} descendant The node to test presence of. + * @return {boolean} Whether the parent node contains the descendent node. + */ +goog.dom.DomHelper.prototype.contains = goog.dom.contains; + + +/** + * Compares the document order of two nodes, returning 0 if they are the same + * node, a negative number if node1 is before node2, and a positive number if + * node2 is before node1. Note that we compare the order the tags appear in the + * document so in the tree <b><i>text</i></b> the B node is considered to be + * before the I node. + * + * @param {Node} node1 The first node to compare. + * @param {Node} node2 The second node to compare. + * @return {number} 0 if the nodes are the same node, a negative number if node1 + * is before node2, and a positive number if node2 is before node1. + */ +goog.dom.DomHelper.prototype.compareNodeOrder = goog.dom.compareNodeOrder; + + +/** + * Find the deepest common ancestor of the given nodes. + * @param {...Node} var_args The nodes to find a common ancestor of. + * @return {Node} The common ancestor of the nodes, or null if there is none. + * null will only be returned if two or more of the nodes are from different + * documents. + */ +goog.dom.DomHelper.prototype.findCommonAncestor = goog.dom.findCommonAncestor; + + +/** + * Returns the owner document for a node. + * @param {Node} node The node to get the document for. + * @return {!Document} The document owning the node. + */ +goog.dom.DomHelper.prototype.getOwnerDocument = goog.dom.getOwnerDocument; + + +/** + * Cross browser function for getting the document element of an iframe. + * @param {Element} iframe Iframe element. + * @return {!Document} The frame content document. + */ +goog.dom.DomHelper.prototype.getFrameContentDocument = + goog.dom.getFrameContentDocument; + + +/** + * Cross browser function for getting the window of a frame or iframe. + * @param {Element} frame Frame element. + * @return {Window} The window associated with the given frame. + */ +goog.dom.DomHelper.prototype.getFrameContentWindow = + goog.dom.getFrameContentWindow; + + +/** + * Sets the text content of a node, with cross-browser support. + * @param {Node} node The node to change the text content of. + * @param {string|number} text The value that should replace the node's content. + */ +goog.dom.DomHelper.prototype.setTextContent = goog.dom.setTextContent; + + +/** + * Gets the outerHTML of a node, which islike innerHTML, except that it + * actually contains the HTML of the node itself. + * @param {Element} element The element to get the HTML of. + * @return {string} The outerHTML of the given element. + */ +goog.dom.DomHelper.prototype.getOuterHtml = goog.dom.getOuterHtml; + + +/** + * Finds the first descendant node that matches the filter function. This does + * a depth first search. + * @param {Node} root The root of the tree to search. + * @param {function(Node) : boolean} p The filter function. + * @return {Node|undefined} The found node or undefined if none is found. + */ +goog.dom.DomHelper.prototype.findNode = goog.dom.findNode; + + +/** + * Finds all the descendant nodes that matches the filter function. This does a + * depth first search. + * @param {Node} root The root of the tree to search. + * @param {function(Node) : boolean} p The filter function. + * @return {Array.<Node>} The found nodes or an empty array if none are found. + */ +goog.dom.DomHelper.prototype.findNodes = goog.dom.findNodes; + + +/** + * Returns true if the element has a tab index that allows it to receive + * keyboard focus (tabIndex >= 0), false otherwise. Note that some elements + * natively support keyboard focus, even if they have no tab index. + * @param {Element} element Element to check. + * @return {boolean} Whether the element has a tab index that allows keyboard + * focus. + */ +goog.dom.DomHelper.prototype.isFocusableTabIndex = goog.dom.isFocusableTabIndex; + + +/** + * Enables or disables keyboard focus support on the element via its tab index. + * Only elements for which {@link goog.dom.isFocusableTabIndex} returns true + * (or elements that natively support keyboard focus, like form elements) can + * receive keyboard focus. See http://go/tabindex for more info. + * @param {Element} element Element whose tab index is to be changed. + * @param {boolean} enable Whether to set or remove a tab index on the element + * that supports keyboard focus. + */ +goog.dom.DomHelper.prototype.setFocusableTabIndex = + goog.dom.setFocusableTabIndex; + + +/** + * Returns true if the element can be focused, i.e. it has a tab index that + * allows it to receive keyboard focus (tabIndex >= 0), or it is an element + * that natively supports keyboard focus. + * @param {Element} element Element to check. + * @return {boolean} Whether the element allows keyboard focus. + */ +goog.dom.DomHelper.prototype.isFocusable = goog.dom.isFocusable; + + +/** + * Returns the text contents of the current node, without markup. New lines are + * stripped and whitespace is collapsed, such that each character would be + * visible. + * + * In browsers that support it, innerText is used. Other browsers attempt to + * simulate it via node traversal. Line breaks are canonicalized in IE. + * + * @param {Node} node The node from which we are getting content. + * @return {string} The text content. + */ +goog.dom.DomHelper.prototype.getTextContent = goog.dom.getTextContent; + + +/** + * Returns the text length of the text contained in a node, without markup. This + * is equivalent to the selection length if the node was selected, or the number + * of cursor movements to traverse the node. Images & BRs take one space. New + * lines are ignored. + * + * @param {Node} node The node whose text content length is being calculated. + * @return {number} The length of {@code node}'s text content. + */ +goog.dom.DomHelper.prototype.getNodeTextLength = goog.dom.getNodeTextLength; + + +/** + * Returns the text offset of a node relative to one of its ancestors. The text + * length is the same as the length calculated by + * {@code goog.dom.getNodeTextLength}. + * + * @param {Node} node The node whose offset is being calculated. + * @param {Node=} opt_offsetParent Defaults to the node's owner document's body. + * @return {number} The text offset. + */ +goog.dom.DomHelper.prototype.getNodeTextOffset = goog.dom.getNodeTextOffset; + + +/** + * Returns the node at a given offset in a parent node. If an object is + * provided for the optional third parameter, the node and the remainder of the + * offset will stored as properties of this object. + * @param {Node} parent The parent node. + * @param {number} offset The offset into the parent node. + * @param {Object=} opt_result Object to be used to store the return value. The + * return value will be stored in the form {node: Node, remainder: number} + * if this object is provided. + * @return {Node} The node at the given offset. + */ +goog.dom.DomHelper.prototype.getNodeAtOffset = goog.dom.getNodeAtOffset; + + +/** + * Returns true if the object is a {@code NodeList}. To qualify as a NodeList, + * the object must have a numeric length property and an item function (which + * has type 'string' on IE for some reason). + * @param {Object} val Object to test. + * @return {boolean} Whether the object is a NodeList. + */ +goog.dom.DomHelper.prototype.isNodeList = goog.dom.isNodeList; + + +/** + * Walks up the DOM hierarchy returning the first ancestor that has the passed + * tag name and/or class name. If the passed element matches the specified + * criteria, the element itself is returned. + * @param {Node} element The DOM node to start with. + * @param {?(goog.dom.TagName|string)=} opt_tag The tag name to match (or + * null/undefined to match only based on class name). + * @param {?string=} opt_class The class name to match (or null/undefined to + * match only based on tag name). + * @return {Element} The first ancestor that matches the passed criteria, or + * null if no match is found. + */ +goog.dom.DomHelper.prototype.getAncestorByTagNameAndClass = + goog.dom.getAncestorByTagNameAndClass; + + +/** + * Walks up the DOM hierarchy returning the first ancestor that has the passed + * class name. If the passed element matches the specified criteria, the + * element itself is returned. + * @param {Node} element The DOM node to start with. + * @param {string} class The class name to match. + * @return {Element} The first ancestor that matches the passed criteria, or + * null if none match. + */ +goog.dom.DomHelper.prototype.getAncestorByClass = + goog.dom.getAncestorByClass; + + +/** + * Walks up the DOM hierarchy returning the first ancestor that passes the + * matcher function. + * @param {Node} element The DOM node to start with. + * @param {function(Node) : boolean} matcher A function that returns true if the + * passed node matches the desired criteria. + * @param {boolean=} opt_includeNode If true, the node itself is included in + * the search (the first call to the matcher will pass startElement as + * the node to test). + * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the + * dom. + * @return {Node} DOM node that matched the matcher, or null if there was + * no match. + */ +goog.dom.DomHelper.prototype.getAncestor = goog.dom.getAncestor; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/nodetype.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/nodetype.js new file mode 100644 index 0000000..cccb470 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/nodetype.js @@ -0,0 +1,48 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Definition of goog.dom.NodeType. + */ + +goog.provide('goog.dom.NodeType'); + + +/** + * Constants for the nodeType attribute in the Node interface. + * + * These constants match those specified in the Node interface. These are + * usually present on the Node object in recent browsers, but not in older + * browsers (specifically, early IEs) and thus are given here. + * + * In some browsers (early IEs), these are not defined on the Node object, + * so they are provided here. + * + * See http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-1950641247 + * @enum {number} + */ +goog.dom.NodeType = { + ELEMENT: 1, + ATTRIBUTE: 2, + TEXT: 3, + CDATA_SECTION: 4, + ENTITY_REFERENCE: 5, + ENTITY: 6, + PROCESSING_INSTRUCTION: 7, + COMMENT: 8, + DOCUMENT: 9, + DOCUMENT_TYPE: 10, + DOCUMENT_FRAGMENT: 11, + NOTATION: 12 +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/tagname.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/tagname.js new file mode 100644 index 0000000..77a9b47 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/tagname.js @@ -0,0 +1,159 @@ +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Defines the goog.dom.TagName enum. This enumerates + * all HTML tag names specified in either the the W3C HTML 4.01 index of + * elements or the HTML5 draft specification. + * + * References: + * http://www.w3.org/TR/html401/index/elements.html + * http://dev.w3.org/html5/spec/section-index.html + * + */ +goog.provide('goog.dom.TagName'); + + +/** + * Enum of all html tag names specified by the W3C HTML4.01 and HTML5 + * specifications. + * @enum {string} + */ +goog.dom.TagName = { + A: 'A', + ABBR: 'ABBR', + ACRONYM: 'ACRONYM', + ADDRESS: 'ADDRESS', + APPLET: 'APPLET', + AREA: 'AREA', + ARTICLE: 'ARTICLE', + ASIDE: 'ASIDE', + AUDIO: 'AUDIO', + B: 'B', + BASE: 'BASE', + BASEFONT: 'BASEFONT', + BDI: 'BDI', + BDO: 'BDO', + BIG: 'BIG', + BLOCKQUOTE: 'BLOCKQUOTE', + BODY: 'BODY', + BR: 'BR', + BUTTON: 'BUTTON', + CANVAS: 'CANVAS', + CAPTION: 'CAPTION', + CENTER: 'CENTER', + CITE: 'CITE', + CODE: 'CODE', + COL: 'COL', + COLGROUP: 'COLGROUP', + COMMAND: 'COMMAND', + DATA: 'DATA', + DATALIST: 'DATALIST', + DD: 'DD', + DEL: 'DEL', + DETAILS: 'DETAILS', + DFN: 'DFN', + DIALOG: 'DIALOG', + DIR: 'DIR', + DIV: 'DIV', + DL: 'DL', + DT: 'DT', + EM: 'EM', + EMBED: 'EMBED', + FIELDSET: 'FIELDSET', + FIGCAPTION: 'FIGCAPTION', + FIGURE: 'FIGURE', + FONT: 'FONT', + FOOTER: 'FOOTER', + FORM: 'FORM', + FRAME: 'FRAME', + FRAMESET: 'FRAMESET', + H1: 'H1', + H2: 'H2', + H3: 'H3', + H4: 'H4', + H5: 'H5', + H6: 'H6', + HEAD: 'HEAD', + HEADER: 'HEADER', + HGROUP: 'HGROUP', + HR: 'HR', + HTML: 'HTML', + I: 'I', + IFRAME: 'IFRAME', + IMG: 'IMG', + INPUT: 'INPUT', + INS: 'INS', + ISINDEX: 'ISINDEX', + KBD: 'KBD', + KEYGEN: 'KEYGEN', + LABEL: 'LABEL', + LEGEND: 'LEGEND', + LI: 'LI', + LINK: 'LINK', + MAP: 'MAP', + MARK: 'MARK', + MATH: 'MATH', + MENU: 'MENU', + META: 'META', + METER: 'METER', + NAV: 'NAV', + NOFRAMES: 'NOFRAMES', + NOSCRIPT: 'NOSCRIPT', + OBJECT: 'OBJECT', + OL: 'OL', + OPTGROUP: 'OPTGROUP', + OPTION: 'OPTION', + OUTPUT: 'OUTPUT', + P: 'P', + PARAM: 'PARAM', + PRE: 'PRE', + PROGRESS: 'PROGRESS', + Q: 'Q', + RP: 'RP', + RT: 'RT', + RUBY: 'RUBY', + S: 'S', + SAMP: 'SAMP', + SCRIPT: 'SCRIPT', + SECTION: 'SECTION', + SELECT: 'SELECT', + SMALL: 'SMALL', + SOURCE: 'SOURCE', + SPAN: 'SPAN', + STRIKE: 'STRIKE', + STRONG: 'STRONG', + STYLE: 'STYLE', + SUB: 'SUB', + SUMMARY: 'SUMMARY', + SUP: 'SUP', + SVG: 'SVG', + TABLE: 'TABLE', + TBODY: 'TBODY', + TD: 'TD', + TEXTAREA: 'TEXTAREA', + TFOOT: 'TFOOT', + TH: 'TH', + THEAD: 'THEAD', + TIME: 'TIME', + TITLE: 'TITLE', + TR: 'TR', + TRACK: 'TRACK', + TT: 'TT', + U: 'U', + UL: 'UL', + VAR: 'VAR', + VIDEO: 'VIDEO', + WBR: 'WBR' +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/vendor.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/vendor.js new file mode 100644 index 0000000..7c1123e --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/dom/vendor.js @@ -0,0 +1,96 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Vendor prefix getters. + */ + +goog.provide('goog.dom.vendor'); + +goog.require('goog.string'); +goog.require('goog.userAgent'); + + +/** + * Returns the JS vendor prefix used in CSS properties. Different vendors + * use different methods of changing the case of the property names. + * + * @return {?string} The JS vendor prefix or null if there is none. + */ +goog.dom.vendor.getVendorJsPrefix = function() { + if (goog.userAgent.WEBKIT) { + return 'Webkit'; + } else if (goog.userAgent.GECKO) { + return 'Moz'; + } else if (goog.userAgent.IE) { + return 'ms'; + } else if (goog.userAgent.OPERA) { + return 'O'; + } + + return null; +}; + + +/** + * Returns the vendor prefix used in CSS properties. + * + * @return {?string} The vendor prefix or null if there is none. + */ +goog.dom.vendor.getVendorPrefix = function() { + if (goog.userAgent.WEBKIT) { + return '-webkit'; + } else if (goog.userAgent.GECKO) { + return '-moz'; + } else if (goog.userAgent.IE) { + return '-ms'; + } else if (goog.userAgent.OPERA) { + return '-o'; + } + + return null; +}; + + +/** + * @param {string} propertyName A property name. + * @param {!Object=} opt_object If provided, we verify if the property exists in + * the object. + * @return {?string} A vendor prefixed property name, or null if it does not + * exist. + */ +goog.dom.vendor.getPrefixedPropertyName = function(propertyName, opt_object) { + // We first check for a non-prefixed property, if available. + if (opt_object && propertyName in opt_object) { + return propertyName; + } + var prefix = goog.dom.vendor.getVendorJsPrefix(); + if (prefix) { + prefix = prefix.toLowerCase(); + var prefixedPropertyName = prefix + goog.string.toTitleCase(propertyName); + return (!goog.isDef(opt_object) || prefixedPropertyName in opt_object) ? + prefixedPropertyName : null; + } + return null; +}; + + +/** + * @param {string} eventType An event type. + * @return {string} A lower-cased vendor prefixed event type. + */ +goog.dom.vendor.getPrefixedEventType = function(eventType) { + var prefix = goog.dom.vendor.getVendorJsPrefix() || ''; + return (prefix + eventType).toLowerCase(); +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/browserevent.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/browserevent.js new file mode 100644 index 0000000..a04b31f --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/browserevent.js @@ -0,0 +1,390 @@ +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A patched, standardized event object for browser events. + * + * <pre> + * The patched event object contains the following members: + * - type {string} Event type, e.g. 'click' + * - target {Object} The element that actually triggered the event + * - currentTarget {Object} The element the listener is attached to + * - relatedTarget {Object} For mouseover and mouseout, the previous object + * - offsetX {number} X-coordinate relative to target + * - offsetY {number} Y-coordinate relative to target + * - clientX {number} X-coordinate relative to viewport + * - clientY {number} Y-coordinate relative to viewport + * - screenX {number} X-coordinate relative to the edge of the screen + * - screenY {number} Y-coordinate relative to the edge of the screen + * - button {number} Mouse button. Use isButton() to test. + * - keyCode {number} Key-code + * - ctrlKey {boolean} Was ctrl key depressed + * - altKey {boolean} Was alt key depressed + * - shiftKey {boolean} Was shift key depressed + * - metaKey {boolean} Was meta key depressed + * - defaultPrevented {boolean} Whether the default action has been prevented + * - state {Object} History state object + * + * NOTE: The keyCode member contains the raw browser keyCode. For normalized + * key and character code use {@link goog.events.KeyHandler}. + * </pre> + * + */ + +goog.provide('goog.events.BrowserEvent'); +goog.provide('goog.events.BrowserEvent.MouseButton'); + +goog.require('goog.events.BrowserFeature'); +goog.require('goog.events.Event'); +goog.require('goog.events.EventType'); +goog.require('goog.reflect'); +goog.require('goog.userAgent'); + + + +/** + * Accepts a browser event object and creates a patched, cross browser event + * object. + * The content of this object will not be initialized if no event object is + * provided. If this is the case, init() needs to be invoked separately. + * @param {Event=} opt_e Browser event object. + * @param {EventTarget=} opt_currentTarget Current target for event. + * @constructor + * @extends {goog.events.Event} + */ +goog.events.BrowserEvent = function(opt_e, opt_currentTarget) { + goog.events.BrowserEvent.base(this, 'constructor', opt_e ? opt_e.type : ''); + + /** + * Target that fired the event. + * @override + * @type {Node} + */ + this.target = null; + + /** + * Node that had the listener attached. + * @override + * @type {Node|undefined} + */ + this.currentTarget = null; + + /** + * For mouseover and mouseout events, the related object for the event. + * @type {Node} + */ + this.relatedTarget = null; + + /** + * X-coordinate relative to target. + * @type {number} + */ + this.offsetX = 0; + + /** + * Y-coordinate relative to target. + * @type {number} + */ + this.offsetY = 0; + + /** + * X-coordinate relative to the window. + * @type {number} + */ + this.clientX = 0; + + /** + * Y-coordinate relative to the window. + * @type {number} + */ + this.clientY = 0; + + /** + * X-coordinate relative to the monitor. + * @type {number} + */ + this.screenX = 0; + + /** + * Y-coordinate relative to the monitor. + * @type {number} + */ + this.screenY = 0; + + /** + * Which mouse button was pressed. + * @type {number} + */ + this.button = 0; + + /** + * Keycode of key press. + * @type {number} + */ + this.keyCode = 0; + + /** + * Keycode of key press. + * @type {number} + */ + this.charCode = 0; + + /** + * Whether control was pressed at time of event. + * @type {boolean} + */ + this.ctrlKey = false; + + /** + * Whether alt was pressed at time of event. + * @type {boolean} + */ + this.altKey = false; + + /** + * Whether shift was pressed at time of event. + * @type {boolean} + */ + this.shiftKey = false; + + /** + * Whether the meta key was pressed at time of event. + * @type {boolean} + */ + this.metaKey = false; + + /** + * History state object, only set for PopState events where it's a copy of the + * state object provided to pushState or replaceState. + * @type {Object} + */ + this.state = null; + + /** + * Whether the default platform modifier key was pressed at time of event. + * (This is control for all platforms except Mac, where it's Meta.) + * @type {boolean} + */ + this.platformModifierKey = false; + + /** + * The browser event object. + * @private {Event} + */ + this.event_ = null; + + if (opt_e) { + this.init(opt_e, opt_currentTarget); + } +}; +goog.inherits(goog.events.BrowserEvent, goog.events.Event); + + +/** + * Normalized button constants for the mouse. + * @enum {number} + */ +goog.events.BrowserEvent.MouseButton = { + LEFT: 0, + MIDDLE: 1, + RIGHT: 2 +}; + + +/** + * Static data for mapping mouse buttons. + * @type {!Array.<number>} + */ +goog.events.BrowserEvent.IEButtonMap = [ + 1, // LEFT + 4, // MIDDLE + 2 // RIGHT +]; + + +/** + * Accepts a browser event object and creates a patched, cross browser event + * object. + * @param {Event} e Browser event object. + * @param {EventTarget=} opt_currentTarget Current target for event. + */ +goog.events.BrowserEvent.prototype.init = function(e, opt_currentTarget) { + var type = this.type = e.type; + + // TODO(nicksantos): Change this.target to type EventTarget. + this.target = /** @type {Node} */ (e.target) || e.srcElement; + + // TODO(nicksantos): Change this.currentTarget to type EventTarget. + this.currentTarget = /** @type {Node} */ (opt_currentTarget); + + var relatedTarget = /** @type {Node} */ (e.relatedTarget); + if (relatedTarget) { + // There's a bug in FireFox where sometimes, relatedTarget will be a + // chrome element, and accessing any property of it will get a permission + // denied exception. See: + // https://bugzilla.mozilla.org/show_bug.cgi?id=497780 + if (goog.userAgent.GECKO) { + if (!goog.reflect.canAccessProperty(relatedTarget, 'nodeName')) { + relatedTarget = null; + } + } + // TODO(arv): Use goog.events.EventType when it has been refactored into its + // own file. + } else if (type == goog.events.EventType.MOUSEOVER) { + relatedTarget = e.fromElement; + } else if (type == goog.events.EventType.MOUSEOUT) { + relatedTarget = e.toElement; + } + + this.relatedTarget = relatedTarget; + + // Webkit emits a lame warning whenever layerX/layerY is accessed. + // http://code.google.com/p/chromium/issues/detail?id=101733 + this.offsetX = (goog.userAgent.WEBKIT || e.offsetX !== undefined) ? + e.offsetX : e.layerX; + this.offsetY = (goog.userAgent.WEBKIT || e.offsetY !== undefined) ? + e.offsetY : e.layerY; + + this.clientX = e.clientX !== undefined ? e.clientX : e.pageX; + this.clientY = e.clientY !== undefined ? e.clientY : e.pageY; + this.screenX = e.screenX || 0; + this.screenY = e.screenY || 0; + + this.button = e.button; + + this.keyCode = e.keyCode || 0; + this.charCode = e.charCode || (type == 'keypress' ? e.keyCode : 0); + this.ctrlKey = e.ctrlKey; + this.altKey = e.altKey; + this.shiftKey = e.shiftKey; + this.metaKey = e.metaKey; + this.platformModifierKey = goog.userAgent.MAC ? e.metaKey : e.ctrlKey; + this.state = e.state; + this.event_ = e; + if (e.defaultPrevented) { + this.preventDefault(); + } +}; + + +/** + * Tests to see which button was pressed during the event. This is really only + * useful in IE and Gecko browsers. And in IE, it's only useful for + * mousedown/mouseup events, because click only fires for the left mouse button. + * + * Safari 2 only reports the left button being clicked, and uses the value '1' + * instead of 0. Opera only reports a mousedown event for the middle button, and + * no mouse events for the right button. Opera has default behavior for left and + * middle click that can only be overridden via a configuration setting. + * + * There's a nice table of this mess at http://www.unixpapa.com/js/mouse.html. + * + * @param {goog.events.BrowserEvent.MouseButton} button The button + * to test for. + * @return {boolean} True if button was pressed. + */ +goog.events.BrowserEvent.prototype.isButton = function(button) { + if (!goog.events.BrowserFeature.HAS_W3C_BUTTON) { + if (this.type == 'click') { + return button == goog.events.BrowserEvent.MouseButton.LEFT; + } else { + return !!(this.event_.button & + goog.events.BrowserEvent.IEButtonMap[button]); + } + } else { + return this.event_.button == button; + } +}; + + +/** + * Whether this has an "action"-producing mouse button. + * + * By definition, this includes left-click on windows/linux, and left-click + * without the ctrl key on Macs. + * + * @return {boolean} The result. + */ +goog.events.BrowserEvent.prototype.isMouseActionButton = function() { + // Webkit does not ctrl+click to be a right-click, so we + // normalize it to behave like Gecko and Opera. + return this.isButton(goog.events.BrowserEvent.MouseButton.LEFT) && + !(goog.userAgent.WEBKIT && goog.userAgent.MAC && this.ctrlKey); +}; + + +/** + * @override + */ +goog.events.BrowserEvent.prototype.stopPropagation = function() { + goog.events.BrowserEvent.superClass_.stopPropagation.call(this); + if (this.event_.stopPropagation) { + this.event_.stopPropagation(); + } else { + this.event_.cancelBubble = true; + } +}; + + +/** + * @override + */ +goog.events.BrowserEvent.prototype.preventDefault = function() { + goog.events.BrowserEvent.superClass_.preventDefault.call(this); + var be = this.event_; + if (!be.preventDefault) { + be.returnValue = false; + if (goog.events.BrowserFeature.SET_KEY_CODE_TO_PREVENT_DEFAULT) { + /** @preserveTry */ + try { + // Most keys can be prevented using returnValue. Some special keys + // require setting the keyCode to -1 as well: + // + // In IE7: + // F3, F5, F10, F11, Ctrl+P, Crtl+O, Ctrl+F (these are taken from IE6) + // + // In IE8: + // Ctrl+P, Crtl+O, Ctrl+F (F1-F12 cannot be stopped through the event) + // + // We therefore do this for all function keys as well as when Ctrl key + // is pressed. + var VK_F1 = 112; + var VK_F12 = 123; + if (be.ctrlKey || be.keyCode >= VK_F1 && be.keyCode <= VK_F12) { + be.keyCode = -1; + } + } catch (ex) { + // IE throws an 'access denied' exception when trying to change + // keyCode in some situations (e.g. srcElement is input[type=file], + // or srcElement is an anchor tag rewritten by parent's innerHTML). + // Do nothing in this case. + } + } + } else { + be.preventDefault(); + } +}; + + +/** + * @return {Event} The underlying browser event object. + */ +goog.events.BrowserEvent.prototype.getBrowserEvent = function() { + return this.event_; +}; + + +/** @override */ +goog.events.BrowserEvent.prototype.disposeInternal = function() { +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/browserfeature.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/browserfeature.js new file mode 100644 index 0000000..61b9d60 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/browserfeature.js @@ -0,0 +1,85 @@ +// Copyright 2010 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Browser capability checks for the events package. + * + */ + + +goog.provide('goog.events.BrowserFeature'); + +goog.require('goog.userAgent'); + + +/** + * Enum of browser capabilities. + * @enum {boolean} + */ +goog.events.BrowserFeature = { + /** + * Whether the button attribute of the event is W3C compliant. False in + * Internet Explorer prior to version 9; document-version dependent. + */ + HAS_W3C_BUTTON: !goog.userAgent.IE || + goog.userAgent.isDocumentModeOrHigher(9), + + /** + * Whether the browser supports full W3C event model. + */ + HAS_W3C_EVENT_SUPPORT: !goog.userAgent.IE || + goog.userAgent.isDocumentModeOrHigher(9), + + /** + * To prevent default in IE7-8 for certain keydown events we need set the + * keyCode to -1. + */ + SET_KEY_CODE_TO_PREVENT_DEFAULT: goog.userAgent.IE && + !goog.userAgent.isVersionOrHigher('9'), + + /** + * Whether the {@code navigator.onLine} property is supported. + */ + HAS_NAVIGATOR_ONLINE_PROPERTY: !goog.userAgent.WEBKIT || + goog.userAgent.isVersionOrHigher('528'), + + /** + * Whether HTML5 network online/offline events are supported. + */ + HAS_HTML5_NETWORK_EVENT_SUPPORT: + goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9b') || + goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8') || + goog.userAgent.OPERA && goog.userAgent.isVersionOrHigher('9.5') || + goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('528'), + + /** + * Whether HTML5 network events fire on document.body, or otherwise the + * window. + */ + HTML5_NETWORK_EVENTS_FIRE_ON_BODY: + goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('8') || + goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9'), + + /** + * Whether touch is enabled in the browser. + */ + TOUCH_ENABLED: + ('ontouchstart' in goog.global || + !!(goog.global['document'] && + document.documentElement && + 'ontouchstart' in document.documentElement) || + // IE10 uses non-standard touch events, so it has a different check. + !!(goog.global['navigator'] && + goog.global['navigator']['msMaxTouchPoints'])) +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/event.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/event.js new file mode 100644 index 0000000..0e55fdc --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/event.js @@ -0,0 +1,161 @@ +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A base class for event objects. + * + */ + + +goog.provide('goog.events.Event'); +goog.provide('goog.events.EventLike'); + +/** + * goog.events.Event no longer depends on goog.Disposable. Keep requiring + * goog.Disposable here to not break projects which assume this dependency. + * @suppress {extraRequire} + */ +goog.require('goog.Disposable'); +goog.require('goog.events.EventId'); + + +/** + * A typedef for event like objects that are dispatchable via the + * goog.events.dispatchEvent function. strings are treated as the type for a + * goog.events.Event. Objects are treated as an extension of a new + * goog.events.Event with the type property of the object being used as the type + * of the Event. + * @typedef {string|Object|goog.events.Event|goog.events.EventId} + */ +goog.events.EventLike; + + + +/** + * A base class for event objects, so that they can support preventDefault and + * stopPropagation. + * + * @param {string|!goog.events.EventId} type Event Type. + * @param {Object=} opt_target Reference to the object that is the target of + * this event. It has to implement the {@code EventTarget} interface + * declared at {@link http://developer.mozilla.org/en/DOM/EventTarget}. + * @constructor + */ +goog.events.Event = function(type, opt_target) { + /** + * Event type. + * @type {string} + */ + this.type = type instanceof goog.events.EventId ? String(type) : type; + + /** + * TODO(user): The type should probably be + * EventTarget|goog.events.EventTarget. + * + * Target of the event. + * @type {Object|undefined} + */ + this.target = opt_target; + + /** + * Object that had the listener attached. + * @type {Object|undefined} + */ + this.currentTarget = this.target; + + /** + * Whether to cancel the event in internal capture/bubble processing for IE. + * @type {boolean} + * @public + * @suppress {underscore|visibility} Technically public, but referencing this + * outside this package is strongly discouraged. + */ + this.propagationStopped_ = false; + + /** + * Whether the default action has been prevented. + * This is a property to match the W3C specification at + * {@link http://www.w3.org/TR/DOM-Level-3-Events/ + * #events-event-type-defaultPrevented}. + * Must be treated as read-only outside the class. + * @type {boolean} + */ + this.defaultPrevented = false; + + /** + * Return value for in internal capture/bubble processing for IE. + * @type {boolean} + * @public + * @suppress {underscore|visibility} Technically public, but referencing this + * outside this package is strongly discouraged. + */ + this.returnValue_ = true; +}; + + +/** + * For backwards compatibility (goog.events.Event used to inherit + * goog.Disposable). + * @deprecated Events don't need to be disposed. + */ +goog.events.Event.prototype.disposeInternal = function() { +}; + + +/** + * For backwards compatibility (goog.events.Event used to inherit + * goog.Disposable). + * @deprecated Events don't need to be disposed. + */ +goog.events.Event.prototype.dispose = function() { +}; + + +/** + * Stops event propagation. + */ +goog.events.Event.prototype.stopPropagation = function() { + this.propagationStopped_ = true; +}; + + +/** + * Prevents the default action, for example a link redirecting to a url. + */ +goog.events.Event.prototype.preventDefault = function() { + this.defaultPrevented = true; + this.returnValue_ = false; +}; + + +/** + * Stops the propagation of the event. It is equivalent to + * {@code e.stopPropagation()}, but can be used as the callback argument of + * {@link goog.events.listen} without declaring another function. + * @param {!goog.events.Event} e An event. + */ +goog.events.Event.stopPropagation = function(e) { + e.stopPropagation(); +}; + + +/** + * Prevents the default action. It is equivalent to + * {@code e.preventDefault()}, but can be used as the callback argument of + * {@link goog.events.listen} without declaring another function. + * @param {!goog.events.Event} e An event. + */ +goog.events.Event.preventDefault = function(e) { + e.preventDefault(); +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventhandler.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventhandler.js new file mode 100644 index 0000000..70071f9 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventhandler.js @@ -0,0 +1,461 @@ +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Class to create objects which want to handle multiple events + * and have their listeners easily cleaned up via a dispose method. + * + * Example: + * <pre> + * function Something() { + * Something.base(this); + * + * ... set up object ... + * + * // Add event listeners + * this.listen(this.starEl, goog.events.EventType.CLICK, this.handleStar); + * this.listen(this.headerEl, goog.events.EventType.CLICK, this.expand); + * this.listen(this.collapseEl, goog.events.EventType.CLICK, this.collapse); + * this.listen(this.infoEl, goog.events.EventType.MOUSEOVER, this.showHover); + * this.listen(this.infoEl, goog.events.EventType.MOUSEOUT, this.hideHover); + * } + * goog.inherits(Something, goog.events.EventHandler); + * + * Something.prototype.disposeInternal = function() { + * Something.base(this, 'disposeInternal'); + * goog.dom.removeNode(this.container); + * }; + * + * + * // Then elsewhere: + * + * var activeSomething = null; + * function openSomething() { + * activeSomething = new Something(); + * } + * + * function closeSomething() { + * if (activeSomething) { + * activeSomething.dispose(); // Remove event listeners + * activeSomething = null; + * } + * } + * </pre> + * + */ + +goog.provide('goog.events.EventHandler'); + +goog.require('goog.Disposable'); +goog.require('goog.events'); +goog.require('goog.object'); + +goog.forwardDeclare('goog.events.EventWrapper'); + + + +/** + * Super class for objects that want to easily manage a number of event + * listeners. It allows a short cut to listen and also provides a quick way + * to remove all events listeners belonging to this object. + * @param {SCOPE=} opt_scope Object in whose scope to call the listeners. + * @constructor + * @extends {goog.Disposable} + * @template SCOPE + */ +goog.events.EventHandler = function(opt_scope) { + goog.Disposable.call(this); + // TODO(user): Rename this to this.scope_ and fix the classes in google3 + // that access this private variable. :( + this.handler_ = opt_scope; + + /** + * Keys for events that are being listened to. + * @type {!Object.<!goog.events.Key>} + * @private + */ + this.keys_ = {}; +}; +goog.inherits(goog.events.EventHandler, goog.Disposable); + + +/** + * Utility array used to unify the cases of listening for an array of types + * and listening for a single event, without using recursion or allocating + * an array each time. + * @type {!Array.<string>} + * @const + * @private + */ +goog.events.EventHandler.typeArray_ = []; + + +/** + * Listen to an event on a Listenable. If the function is omitted then the + * EventHandler's handleEvent method will be used. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array.<string>| + * !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>} + * type Event type to listen for or array of event types. + * @param {function(this:SCOPE, EVENTOBJ):?|{handleEvent:function(?):?}|null=} + * opt_fn Optional callback function to be used as the listener or an object + * with handleEvent function. + * @param {boolean=} opt_capture Optional whether to use capture phase. + * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for + * chaining of calls. + * @template EVENTOBJ + */ +goog.events.EventHandler.prototype.listen = function( + src, type, opt_fn, opt_capture) { + return this.listen_(src, type, opt_fn, opt_capture); +}; + + +/** + * Listen to an event on a Listenable. If the function is omitted then the + * EventHandler's handleEvent method will be used. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array.<string>| + * !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>} + * type Event type to listen for or array of event types. + * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(this:T, ?):?}| + * null|undefined} fn Optional callback function to be used as the + * listener or an object with handleEvent function. + * @param {boolean|undefined} capture Optional whether to use capture phase. + * @param {T} scope Object in whose scope to call the listener. + * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for + * chaining of calls. + * @template T,EVENTOBJ + */ +goog.events.EventHandler.prototype.listenWithScope = function( + src, type, fn, capture, scope) { + // TODO(user): Deprecate this function. + return this.listen_(src, type, fn, capture, scope); +}; + + +/** + * Listen to an event on a Listenable. If the function is omitted then the + * EventHandler's handleEvent method will be used. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array.<string>| + * !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>} + * type Event type to listen for or array of event types. + * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn + * Optional callback function to be used as the listener or an object with + * handleEvent function. + * @param {boolean=} opt_capture Optional whether to use capture phase. + * @param {Object=} opt_scope Object in whose scope to call the listener. + * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for + * chaining of calls. + * @template EVENTOBJ + * @private + */ +goog.events.EventHandler.prototype.listen_ = function(src, type, opt_fn, + opt_capture, + opt_scope) { + if (!goog.isArray(type)) { + if (type) { + goog.events.EventHandler.typeArray_[0] = type.toString(); + } + type = goog.events.EventHandler.typeArray_; + } + for (var i = 0; i < type.length; i++) { + var listenerObj = goog.events.listen( + src, type[i], opt_fn || this.handleEvent, + opt_capture || false, + opt_scope || this.handler_ || this); + + if (!listenerObj) { + // When goog.events.listen run on OFF_AND_FAIL or OFF_AND_SILENT + // (goog.events.CaptureSimulationMode) in IE8-, it will return null + // value. + return this; + } + + var key = listenerObj.key; + this.keys_[key] = listenerObj; + } + + return this; +}; + + +/** + * Listen to an event on a Listenable. If the function is omitted, then the + * EventHandler's handleEvent method will be used. After the event has fired the + * event listener is removed from the target. If an array of event types is + * provided, each event type will be listened to once. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array.<string>| + * !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>} + * type Event type to listen for or array of event types. + * @param {function(this:SCOPE, EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn + * Optional callback function to be used as the listener or an object with + * handleEvent function. + * @param {boolean=} opt_capture Optional whether to use capture phase. + * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for + * chaining of calls. + * @template EVENTOBJ + */ +goog.events.EventHandler.prototype.listenOnce = function( + src, type, opt_fn, opt_capture) { + // TODO(user): Remove the opt_scope from this function and then + // templatize it. + return this.listenOnce_(src, type, opt_fn, opt_capture); +}; + + +/** + * Listen to an event on a Listenable. If the function is omitted, then the + * EventHandler's handleEvent method will be used. After the event has fired the + * event listener is removed from the target. If an array of event types is + * provided, each event type will be listened to once. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array.<string>| + * !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>} + * type Event type to listen for or array of event types. + * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(this:T, ?):?}| + * null|undefined} fn Optional callback function to be used as the + * listener or an object with handleEvent function. + * @param {boolean|undefined} capture Optional whether to use capture phase. + * @param {T} scope Object in whose scope to call the listener. + * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for + * chaining of calls. + * @template T,EVENTOBJ + */ +goog.events.EventHandler.prototype.listenOnceWithScope = function( + src, type, fn, capture, scope) { + // TODO(user): Deprecate this function. + return this.listenOnce_(src, type, fn, capture, scope); +}; + + +/** + * Listen to an event on a Listenable. If the function is omitted, then the + * EventHandler's handleEvent method will be used. After the event has fired + * the event listener is removed from the target. If an array of event types is + * provided, each event type will be listened to once. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array.<string>| + * !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>} + * type Event type to listen for or array of event types. + * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn + * Optional callback function to be used as the listener or an object with + * handleEvent function. + * @param {boolean=} opt_capture Optional whether to use capture phase. + * @param {Object=} opt_scope Object in whose scope to call the listener. + * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for + * chaining of calls. + * @template EVENTOBJ + * @private + */ +goog.events.EventHandler.prototype.listenOnce_ = function( + src, type, opt_fn, opt_capture, opt_scope) { + if (goog.isArray(type)) { + for (var i = 0; i < type.length; i++) { + this.listenOnce_(src, type[i], opt_fn, opt_capture, opt_scope); + } + } else { + var listenerObj = goog.events.listenOnce( + src, type, opt_fn || this.handleEvent, opt_capture, + opt_scope || this.handler_ || this); + if (!listenerObj) { + // When goog.events.listen run on OFF_AND_FAIL or OFF_AND_SILENT + // (goog.events.CaptureSimulationMode) in IE8-, it will return null + // value. + return this; + } + + var key = listenerObj.key; + this.keys_[key] = listenerObj; + } + + return this; +}; + + +/** + * Adds an event listener with a specific event wrapper on a DOM Node or an + * object that has implemented {@link goog.events.EventTarget}. A listener can + * only be added once to an object. + * + * @param {EventTarget|goog.events.EventTarget} src The node to listen to + * events on. + * @param {goog.events.EventWrapper} wrapper Event wrapper to use. + * @param {function(this:SCOPE, ?):?|{handleEvent:function(?):?}|null} listener + * Callback method, or an object with a handleEvent function. + * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to + * false). + * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for + * chaining of calls. + */ +goog.events.EventHandler.prototype.listenWithWrapper = function( + src, wrapper, listener, opt_capt) { + // TODO(user): Remove the opt_scope from this function and then + // templatize it. + return this.listenWithWrapper_(src, wrapper, listener, opt_capt); +}; + + +/** + * Adds an event listener with a specific event wrapper on a DOM Node or an + * object that has implemented {@link goog.events.EventTarget}. A listener can + * only be added once to an object. + * + * @param {EventTarget|goog.events.EventTarget} src The node to listen to + * events on. + * @param {goog.events.EventWrapper} wrapper Event wrapper to use. + * @param {function(this:T, ?):?|{handleEvent:function(this:T, ?):?}|null} + * listener Optional callback function to be used as the + * listener or an object with handleEvent function. + * @param {boolean|undefined} capture Optional whether to use capture phase. + * @param {T} scope Object in whose scope to call the listener. + * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for + * chaining of calls. + * @template T + */ +goog.events.EventHandler.prototype.listenWithWrapperAndScope = function( + src, wrapper, listener, capture, scope) { + // TODO(user): Deprecate this function. + return this.listenWithWrapper_(src, wrapper, listener, capture, scope); +}; + + +/** + * Adds an event listener with a specific event wrapper on a DOM Node or an + * object that has implemented {@link goog.events.EventTarget}. A listener can + * only be added once to an object. + * + * @param {EventTarget|goog.events.EventTarget} src The node to listen to + * events on. + * @param {goog.events.EventWrapper} wrapper Event wrapper to use. + * @param {function(?):?|{handleEvent:function(?):?}|null} listener Callback + * method, or an object with a handleEvent function. + * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to + * false). + * @param {Object=} opt_scope Element in whose scope to call the listener. + * @return {!goog.events.EventHandler.<SCOPE>} This object, allowing for + * chaining of calls. + * @private + */ +goog.events.EventHandler.prototype.listenWithWrapper_ = function( + src, wrapper, listener, opt_capt, opt_scope) { + wrapper.listen(src, listener, opt_capt, opt_scope || this.handler_ || this, + this); + return this; +}; + + +/** + * @return {number} Number of listeners registered by this handler. + */ +goog.events.EventHandler.prototype.getListenerCount = function() { + var count = 0; + for (var key in this.keys_) { + if (Object.prototype.hasOwnProperty.call(this.keys_, key)) { + count++; + } + } + return count; +}; + + +/** + * Unlistens on an event. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array.<string>| + * !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>} + * type Event type or array of event types to unlisten to. + * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn + * Optional callback function to be used as the listener or an object with + * handleEvent function. + * @param {boolean=} opt_capture Optional whether to use capture phase. + * @param {Object=} opt_scope Object in whose scope to call the listener. + * @return {!goog.events.EventHandler} This object, allowing for chaining of + * calls. + * @template EVENTOBJ + */ +goog.events.EventHandler.prototype.unlisten = function(src, type, opt_fn, + opt_capture, + opt_scope) { + if (goog.isArray(type)) { + for (var i = 0; i < type.length; i++) { + this.unlisten(src, type[i], opt_fn, opt_capture, opt_scope); + } + } else { + var listener = goog.events.getListener(src, type, + opt_fn || this.handleEvent, + opt_capture, opt_scope || this.handler_ || this); + + if (listener) { + goog.events.unlistenByKey(listener); + delete this.keys_[listener.key]; + } + } + + return this; +}; + + +/** + * Removes an event listener which was added with listenWithWrapper(). + * + * @param {EventTarget|goog.events.EventTarget} src The target to stop + * listening to events on. + * @param {goog.events.EventWrapper} wrapper Event wrapper to use. + * @param {function(?):?|{handleEvent:function(?):?}|null} listener The + * listener function to remove. + * @param {boolean=} opt_capt In DOM-compliant browsers, this determines + * whether the listener is fired during the capture or bubble phase of the + * event. + * @param {Object=} opt_scope Element in whose scope to call the listener. + * @return {!goog.events.EventHandler} This object, allowing for chaining of + * calls. + */ +goog.events.EventHandler.prototype.unlistenWithWrapper = function(src, wrapper, + listener, opt_capt, opt_scope) { + wrapper.unlisten(src, listener, opt_capt, + opt_scope || this.handler_ || this, this); + return this; +}; + + +/** + * Unlistens to all events. + */ +goog.events.EventHandler.prototype.removeAll = function() { + goog.object.forEach(this.keys_, goog.events.unlistenByKey); + this.keys_ = {}; +}; + + +/** + * Disposes of this EventHandler and removes all listeners that it registered. + * @override + * @protected + */ +goog.events.EventHandler.prototype.disposeInternal = function() { + goog.events.EventHandler.superClass_.disposeInternal.call(this); + this.removeAll(); +}; + + +/** + * Default event handler + * @param {goog.events.Event} e Event object. + */ +goog.events.EventHandler.prototype.handleEvent = function(e) { + throw Error('EventHandler.handleEvent not implemented'); +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventid.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventid.js new file mode 100644 index 0000000..f7ea4dd --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventid.js @@ -0,0 +1,47 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +goog.provide('goog.events.EventId'); + + + +/** + * A templated class that is used when registering for events. Typical usage: + * <code> + * /** @type {goog.events.EventId.<MyEventObj>} + * var myEventId = new goog.events.EventId( + * goog.events.getUniqueId(('someEvent')); + * + * // No need to cast or declare here since the compiler knows the correct + * // type of 'evt' (MyEventObj). + * something.listen(myEventId, function(evt) {}); + * </code> + * + * @param {string} eventId + * @template T + * @constructor + * @struct + * @final + */ +goog.events.EventId = function(eventId) { + /** @const */ this.id = eventId; +}; + + +/** + * @override + */ +goog.events.EventId.prototype.toString = function() { + return this.id; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/events.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/events.js new file mode 100644 index 0000000..e39d3bd --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/events.js @@ -0,0 +1,995 @@ +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview An event manager for both native browser event + * targets and custom JavaScript event targets + * ({@code goog.events.Listenable}). This provides an abstraction + * over browsers' event systems. + * + * It also provides a simulation of W3C event model's capture phase in + * Internet Explorer (IE 8 and below). Caveat: the simulation does not + * interact well with listeners registered directly on the elements + * (bypassing goog.events) or even with listeners registered via + * goog.events in a separate JS binary. In these cases, we provide + * no ordering guarantees. + * + * The listeners will receive a "patched" event object. Such event object + * contains normalized values for certain event properties that differs in + * different browsers. + * + * Example usage: + * <pre> + * goog.events.listen(myNode, 'click', function(e) { alert('woo') }); + * goog.events.listen(myNode, 'mouseover', mouseHandler, true); + * goog.events.unlisten(myNode, 'mouseover', mouseHandler, true); + * goog.events.removeAll(myNode); + * </pre> + * + * in IE and event object patching] + * + * @see ../demos/events.html + * @see ../demos/event-propagation.html + * @see ../demos/stopevent.html + */ + +// IMPLEMENTATION NOTES: +// goog.events stores an auxiliary data structure on each EventTarget +// source being listened on. This allows us to take advantage of GC, +// having the data structure GC'd when the EventTarget is GC'd. This +// GC behavior is equivalent to using W3C DOM Events directly. + +goog.provide('goog.events'); +goog.provide('goog.events.CaptureSimulationMode'); +goog.provide('goog.events.Key'); +goog.provide('goog.events.ListenableType'); + +goog.require('goog.asserts'); +goog.require('goog.debug.entryPointRegistry'); +goog.require('goog.events.BrowserEvent'); +goog.require('goog.events.BrowserFeature'); +goog.require('goog.events.Listenable'); +goog.require('goog.events.ListenerMap'); + +goog.forwardDeclare('goog.debug.ErrorHandler'); +goog.forwardDeclare('goog.events.EventWrapper'); + + +/** + * @typedef {number|goog.events.ListenableKey} + */ +goog.events.Key; + + +/** + * @typedef {EventTarget|goog.events.Listenable} + */ +goog.events.ListenableType; + + +/** + * Property name on a native event target for the listener map + * associated with the event target. + * @const + * @private + */ +goog.events.LISTENER_MAP_PROP_ = 'closure_lm_' + ((Math.random() * 1e6) | 0); + + +/** + * String used to prepend to IE event types. + * @const + * @private + */ +goog.events.onString_ = 'on'; + + +/** + * Map of computed "on<eventname>" strings for IE event types. Caching + * this removes an extra object allocation in goog.events.listen which + * improves IE6 performance. + * @const + * @dict + * @private + */ +goog.events.onStringMap_ = {}; + + +/** + * @enum {number} Different capture simulation mode for IE8-. + */ +goog.events.CaptureSimulationMode = { + /** + * Does not perform capture simulation. Will asserts in IE8- when you + * add capture listeners. + */ + OFF_AND_FAIL: 0, + + /** + * Does not perform capture simulation, silently ignore capture + * listeners. + */ + OFF_AND_SILENT: 1, + + /** + * Performs capture simulation. + */ + ON: 2 +}; + + +/** + * @define {number} The capture simulation mode for IE8-. By default, + * this is ON. + */ +goog.define('goog.events.CAPTURE_SIMULATION_MODE', 2); + + +/** + * Estimated count of total native listeners. + * @private {number} + */ +goog.events.listenerCountEstimate_ = 0; + + +/** + * Adds an event listener for a specific event on a native event + * target (such as a DOM element) or an object that has implemented + * {@link goog.events.Listenable}. A listener can only be added once + * to an object and if it is added again the key for the listener is + * returned. Note that if the existing listener is a one-off listener + * (registered via listenOnce), it will no longer be a one-off + * listener after a call to listen(). + * + * @param {EventTarget|goog.events.Listenable} src The node to listen + * to events on. + * @param {string|Array.<string>| + * !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>} + * type Event type or array of event types. + * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null} + * listener Callback method, or an object with a handleEvent function. + * WARNING: passing an Object is now softly deprecated. + * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to + * false). + * @param {T=} opt_handler Element in whose scope to call the listener. + * @return {goog.events.Key} Unique key for the listener. + * @template T,EVENTOBJ + */ +goog.events.listen = function(src, type, listener, opt_capt, opt_handler) { + if (goog.isArray(type)) { + for (var i = 0; i < type.length; i++) { + goog.events.listen(src, type[i], listener, opt_capt, opt_handler); + } + return null; + } + + listener = goog.events.wrapListener(listener); + if (goog.events.Listenable.isImplementedBy(src)) { + return src.listen( + /** @type {string|!goog.events.EventId} */ (type), + listener, opt_capt, opt_handler); + } else { + return goog.events.listen_( + /** @type {EventTarget} */ (src), + /** @type {string|!goog.events.EventId} */ (type), + listener, /* callOnce */ false, opt_capt, opt_handler); + } +}; + + +/** + * Adds an event listener for a specific event on a native event + * target. A listener can only be added once to an object and if it + * is added again the key for the listener is returned. + * + * Note that a one-off listener will not change an existing listener, + * if any. On the other hand a normal listener will change existing + * one-off listener to become a normal listener. + * + * @param {EventTarget} src The node to listen to events on. + * @param {string|!goog.events.EventId} type Event type. + * @param {!Function} listener Callback function. + * @param {boolean} callOnce Whether the listener is a one-off + * listener or otherwise. + * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to + * false). + * @param {Object=} opt_handler Element in whose scope to call the listener. + * @return {goog.events.ListenableKey} Unique key for the listener. + * @private + */ +goog.events.listen_ = function( + src, type, listener, callOnce, opt_capt, opt_handler) { + if (!type) { + throw Error('Invalid event type'); + } + + var capture = !!opt_capt; + if (capture && !goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) { + if (goog.events.CAPTURE_SIMULATION_MODE == + goog.events.CaptureSimulationMode.OFF_AND_FAIL) { + goog.asserts.fail('Can not register capture listener in IE8-.'); + return null; + } else if (goog.events.CAPTURE_SIMULATION_MODE == + goog.events.CaptureSimulationMode.OFF_AND_SILENT) { + return null; + } + } + + var listenerMap = goog.events.getListenerMap_(src); + if (!listenerMap) { + src[goog.events.LISTENER_MAP_PROP_] = listenerMap = + new goog.events.ListenerMap(src); + } + + var listenerObj = listenerMap.add( + type, listener, callOnce, opt_capt, opt_handler); + + // If the listenerObj already has a proxy, it has been set up + // previously. We simply return. + if (listenerObj.proxy) { + return listenerObj; + } + + var proxy = goog.events.getProxy(); + listenerObj.proxy = proxy; + + proxy.src = src; + proxy.listener = listenerObj; + + // Attach the proxy through the browser's API + if (src.addEventListener) { + src.addEventListener(type.toString(), proxy, capture); + } else { + // The else above used to be else if (src.attachEvent) and then there was + // another else statement that threw an exception warning the developer + // they made a mistake. This resulted in an extra object allocation in IE6 + // due to a wrapper object that had to be implemented around the element + // and so was removed. + src.attachEvent(goog.events.getOnString_(type.toString()), proxy); + } + + goog.events.listenerCountEstimate_++; + return listenerObj; +}; + + +/** + * Helper function for returning a proxy function. + * @return {!Function} A new or reused function object. + */ +goog.events.getProxy = function() { + var proxyCallbackFunction = goog.events.handleBrowserEvent_; + // Use a local var f to prevent one allocation. + var f = goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT ? + function(eventObject) { + return proxyCallbackFunction.call(f.src, f.listener, eventObject); + } : + function(eventObject) { + var v = proxyCallbackFunction.call(f.src, f.listener, eventObject); + // NOTE(user): In IE, we hack in a capture phase. However, if + // there is inline event handler which tries to prevent default (for + // example <a href="..." onclick="return false">...</a>) in a + // descendant element, the prevent default will be overridden + // by this listener if this listener were to return true. Hence, we + // return undefined. + if (!v) return v; + }; + return f; +}; + + +/** + * Adds an event listener for a specific event on a native event + * target (such as a DOM element) or an object that has implemented + * {@link goog.events.Listenable}. After the event has fired the event + * listener is removed from the target. + * + * If an existing listener already exists, listenOnce will do + * nothing. In particular, if the listener was previously registered + * via listen(), listenOnce() will not turn the listener into a + * one-off listener. Similarly, if there is already an existing + * one-off listener, listenOnce does not modify the listeners (it is + * still a once listener). + * + * @param {EventTarget|goog.events.Listenable} src The node to listen + * to events on. + * @param {string|Array.<string>| + * !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>} + * type Event type or array of event types. + * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null} + * listener Callback method. + * @param {boolean=} opt_capt Fire in capture phase?. + * @param {T=} opt_handler Element in whose scope to call the listener. + * @return {goog.events.Key} Unique key for the listener. + * @template T,EVENTOBJ + */ +goog.events.listenOnce = function(src, type, listener, opt_capt, opt_handler) { + if (goog.isArray(type)) { + for (var i = 0; i < type.length; i++) { + goog.events.listenOnce(src, type[i], listener, opt_capt, opt_handler); + } + return null; + } + + listener = goog.events.wrapListener(listener); + if (goog.events.Listenable.isImplementedBy(src)) { + return src.listenOnce( + /** @type {string|!goog.events.EventId} */ (type), + listener, opt_capt, opt_handler); + } else { + return goog.events.listen_( + /** @type {EventTarget} */ (src), + /** @type {string|!goog.events.EventId} */ (type), + listener, /* callOnce */ true, opt_capt, opt_handler); + } +}; + + +/** + * Adds an event listener with a specific event wrapper on a DOM Node or an + * object that has implemented {@link goog.events.Listenable}. A listener can + * only be added once to an object. + * + * @param {EventTarget|goog.events.Listenable} src The target to + * listen to events on. + * @param {goog.events.EventWrapper} wrapper Event wrapper to use. + * @param {function(this:T, ?):?|{handleEvent:function(?):?}|null} listener + * Callback method, or an object with a handleEvent function. + * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to + * false). + * @param {T=} opt_handler Element in whose scope to call the listener. + * @template T + */ +goog.events.listenWithWrapper = function(src, wrapper, listener, opt_capt, + opt_handler) { + wrapper.listen(src, listener, opt_capt, opt_handler); +}; + + +/** + * Removes an event listener which was added with listen(). + * + * @param {EventTarget|goog.events.Listenable} src The target to stop + * listening to events on. + * @param {string|Array.<string>| + * !goog.events.EventId.<EVENTOBJ>|!Array.<!goog.events.EventId.<EVENTOBJ>>} + * type Event type or array of event types to unlisten to. + * @param {function(?):?|{handleEvent:function(?):?}|null} listener The + * listener function to remove. + * @param {boolean=} opt_capt In DOM-compliant browsers, this determines + * whether the listener is fired during the capture or bubble phase of the + * event. + * @param {Object=} opt_handler Element in whose scope to call the listener. + * @return {?boolean} indicating whether the listener was there to remove. + * @template EVENTOBJ + */ +goog.events.unlisten = function(src, type, listener, opt_capt, opt_handler) { + if (goog.isArray(type)) { + for (var i = 0; i < type.length; i++) { + goog.events.unlisten(src, type[i], listener, opt_capt, opt_handler); + } + return null; + } + + listener = goog.events.wrapListener(listener); + if (goog.events.Listenable.isImplementedBy(src)) { + return src.unlisten( + /** @type {string|!goog.events.EventId} */ (type), + listener, opt_capt, opt_handler); + } + + if (!src) { + // TODO(user): We should tighten the API to only accept + // non-null objects, or add an assertion here. + return false; + } + + var capture = !!opt_capt; + var listenerMap = goog.events.getListenerMap_( + /** @type {EventTarget} */ (src)); + if (listenerMap) { + var listenerObj = listenerMap.getListener( + /** @type {string|!goog.events.EventId} */ (type), + listener, capture, opt_handler); + if (listenerObj) { + return goog.events.unlistenByKey(listenerObj); + } + } + + return false; +}; + + +/** + * Removes an event listener which was added with listen() by the key + * returned by listen(). + * + * @param {goog.events.Key} key The key returned by listen() for this + * event listener. + * @return {boolean} indicating whether the listener was there to remove. + */ +goog.events.unlistenByKey = function(key) { + // TODO(user): Remove this check when tests that rely on this + // are fixed. + if (goog.isNumber(key)) { + return false; + } + + var listener = /** @type {goog.events.ListenableKey} */ (key); + if (!listener || listener.removed) { + return false; + } + + var src = listener.src; + if (goog.events.Listenable.isImplementedBy(src)) { + return src.unlistenByKey(listener); + } + + var type = listener.type; + var proxy = listener.proxy; + if (src.removeEventListener) { + src.removeEventListener(type, proxy, listener.capture); + } else if (src.detachEvent) { + src.detachEvent(goog.events.getOnString_(type), proxy); + } + goog.events.listenerCountEstimate_--; + + var listenerMap = goog.events.getListenerMap_( + /** @type {EventTarget} */ (src)); + // TODO(user): Try to remove this conditional and execute the + // first branch always. This should be safe. + if (listenerMap) { + listenerMap.removeByKey(listener); + if (listenerMap.getTypeCount() == 0) { + // Null the src, just because this is simple to do (and useful + // for IE <= 7). + listenerMap.src = null; + // We don't use delete here because IE does not allow delete + // on a window object. + src[goog.events.LISTENER_MAP_PROP_] = null; + } + } else { + listener.markAsRemoved(); + } + + return true; +}; + + +/** + * Removes an event listener which was added with listenWithWrapper(). + * + * @param {EventTarget|goog.events.Listenable} src The target to stop + * listening to events on. + * @param {goog.events.EventWrapper} wrapper Event wrapper to use. + * @param {function(?):?|{handleEvent:function(?):?}|null} listener The + * listener function to remove. + * @param {boolean=} opt_capt In DOM-compliant browsers, this determines + * whether the listener is fired during the capture or bubble phase of the + * event. + * @param {Object=} opt_handler Element in whose scope to call the listener. + */ +goog.events.unlistenWithWrapper = function(src, wrapper, listener, opt_capt, + opt_handler) { + wrapper.unlisten(src, listener, opt_capt, opt_handler); +}; + + +/** + * Removes all listeners from an object. You can also optionally + * remove listeners of a particular type. + * + * @param {Object|undefined} obj Object to remove listeners from. Must be an + * EventTarget or a goog.events.Listenable. + * @param {string|!goog.events.EventId=} opt_type Type of event to remove. + * Default is all types. + * @return {number} Number of listeners removed. + */ +goog.events.removeAll = function(obj, opt_type) { + // TODO(user): Change the type of obj to + // (!EventTarget|!goog.events.Listenable). + + if (!obj) { + return 0; + } + + if (goog.events.Listenable.isImplementedBy(obj)) { + return obj.removeAllListeners(opt_type); + } + + var listenerMap = goog.events.getListenerMap_( + /** @type {EventTarget} */ (obj)); + if (!listenerMap) { + return 0; + } + + var count = 0; + var typeStr = opt_type && opt_type.toString(); + for (var type in listenerMap.listeners) { + if (!typeStr || type == typeStr) { + // Clone so that we don't need to worry about unlistenByKey + // changing the content of the ListenerMap. + var listeners = listenerMap.listeners[type].concat(); + for (var i = 0; i < listeners.length; ++i) { + if (goog.events.unlistenByKey(listeners[i])) { + ++count; + } + } + } + } + return count; +}; + + +/** + * Removes all native listeners registered via goog.events. Native + * listeners are listeners on native browser objects (such as DOM + * elements). In particular, goog.events.Listenable and + * goog.events.EventTarget listeners will NOT be removed. + * @return {number} Number of listeners removed. + * @deprecated This doesn't do anything, now that Closure no longer + * stores a central listener registry. + */ +goog.events.removeAllNativeListeners = function() { + goog.events.listenerCountEstimate_ = 0; + return 0; +}; + + +/** + * Gets the listeners for a given object, type and capture phase. + * + * @param {Object} obj Object to get listeners for. + * @param {string|!goog.events.EventId} type Event type. + * @param {boolean} capture Capture phase?. + * @return {Array.<goog.events.Listener>} Array of listener objects. + */ +goog.events.getListeners = function(obj, type, capture) { + if (goog.events.Listenable.isImplementedBy(obj)) { + return obj.getListeners(type, capture); + } else { + if (!obj) { + // TODO(user): We should tighten the API to accept + // !EventTarget|goog.events.Listenable, and add an assertion here. + return []; + } + + var listenerMap = goog.events.getListenerMap_( + /** @type {EventTarget} */ (obj)); + return listenerMap ? listenerMap.getListeners(type, capture) : []; + } +}; + + +/** + * Gets the goog.events.Listener for the event or null if no such listener is + * in use. + * + * @param {EventTarget|goog.events.Listenable} src The target from + * which to get listeners. + * @param {?string|!goog.events.EventId.<EVENTOBJ>} type The type of the event. + * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null} listener The + * listener function to get. + * @param {boolean=} opt_capt In DOM-compliant browsers, this determines + * whether the listener is fired during the + * capture or bubble phase of the event. + * @param {Object=} opt_handler Element in whose scope to call the listener. + * @return {goog.events.ListenableKey} the found listener or null if not found. + * @template EVENTOBJ + */ +goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) { + // TODO(user): Change type from ?string to string, or add assertion. + type = /** @type {string} */ (type); + listener = goog.events.wrapListener(listener); + var capture = !!opt_capt; + if (goog.events.Listenable.isImplementedBy(src)) { + return src.getListener(type, listener, capture, opt_handler); + } + + if (!src) { + // TODO(user): We should tighten the API to only accept + // non-null objects, or add an assertion here. + return null; + } + + var listenerMap = goog.events.getListenerMap_( + /** @type {EventTarget} */ (src)); + if (listenerMap) { + return listenerMap.getListener(type, listener, capture, opt_handler); + } + return null; +}; + + +/** + * Returns whether an event target has any active listeners matching the + * specified signature. If either the type or capture parameters are + * unspecified, the function will match on the remaining criteria. + * + * @param {EventTarget|goog.events.Listenable} obj Target to get + * listeners for. + * @param {string|!goog.events.EventId=} opt_type Event type. + * @param {boolean=} opt_capture Whether to check for capture or bubble-phase + * listeners. + * @return {boolean} Whether an event target has one or more listeners matching + * the requested type and/or capture phase. + */ +goog.events.hasListener = function(obj, opt_type, opt_capture) { + if (goog.events.Listenable.isImplementedBy(obj)) { + return obj.hasListener(opt_type, opt_capture); + } + + var listenerMap = goog.events.getListenerMap_( + /** @type {EventTarget} */ (obj)); + return !!listenerMap && listenerMap.hasListener(opt_type, opt_capture); +}; + + +/** + * Provides a nice string showing the normalized event objects public members + * @param {Object} e Event Object. + * @return {string} String of the public members of the normalized event object. + */ +goog.events.expose = function(e) { + var str = []; + for (var key in e) { + if (e[key] && e[key].id) { + str.push(key + ' = ' + e[key] + ' (' + e[key].id + ')'); + } else { + str.push(key + ' = ' + e[key]); + } + } + return str.join('\n'); +}; + + +/** + * Returns a string with on prepended to the specified type. This is used for IE + * which expects "on" to be prepended. This function caches the string in order + * to avoid extra allocations in steady state. + * @param {string} type Event type. + * @return {string} The type string with 'on' prepended. + * @private + */ +goog.events.getOnString_ = function(type) { + if (type in goog.events.onStringMap_) { + return goog.events.onStringMap_[type]; + } + return goog.events.onStringMap_[type] = goog.events.onString_ + type; +}; + + +/** + * Fires an object's listeners of a particular type and phase + * + * @param {Object} obj Object whose listeners to call. + * @param {string|!goog.events.EventId} type Event type. + * @param {boolean} capture Which event phase. + * @param {Object} eventObject Event object to be passed to listener. + * @return {boolean} True if all listeners returned true else false. + */ +goog.events.fireListeners = function(obj, type, capture, eventObject) { + if (goog.events.Listenable.isImplementedBy(obj)) { + return obj.fireListeners(type, capture, eventObject); + } + + return goog.events.fireListeners_(obj, type, capture, eventObject); +}; + + +/** + * Fires an object's listeners of a particular type and phase. + * @param {Object} obj Object whose listeners to call. + * @param {string|!goog.events.EventId} type Event type. + * @param {boolean} capture Which event phase. + * @param {Object} eventObject Event object to be passed to listener. + * @return {boolean} True if all listeners returned true else false. + * @private + */ +goog.events.fireListeners_ = function(obj, type, capture, eventObject) { + var retval = 1; + + var listenerMap = goog.events.getListenerMap_( + /** @type {EventTarget} */ (obj)); + if (listenerMap) { + // TODO(user): Original code avoids array creation when there + // is no listener, so we do the same. If this optimization turns + // out to be not required, we can replace this with + // listenerMap.getListeners(type, capture) instead, which is simpler. + var listenerArray = listenerMap.listeners[type.toString()]; + if (listenerArray) { + listenerArray = listenerArray.concat(); + for (var i = 0; i < listenerArray.length; i++) { + var listener = listenerArray[i]; + // We might not have a listener if the listener was removed. + if (listener && listener.capture == capture && !listener.removed) { + retval &= + goog.events.fireListener(listener, eventObject) !== false; + } + } + } + } + return Boolean(retval); +}; + + +/** + * Fires a listener with a set of arguments + * + * @param {goog.events.Listener} listener The listener object to call. + * @param {Object} eventObject The event object to pass to the listener. + * @return {boolean} Result of listener. + */ +goog.events.fireListener = function(listener, eventObject) { + var listenerFn = listener.listener; + var listenerHandler = listener.handler || listener.src; + + if (listener.callOnce) { + goog.events.unlistenByKey(listener); + } + return listenerFn.call(listenerHandler, eventObject); +}; + + +/** + * Gets the total number of listeners currently in the system. + * @return {number} Number of listeners. + * @deprecated This returns estimated count, now that Closure no longer + * stores a central listener registry. We still return an estimation + * to keep existing listener-related tests passing. In the near future, + * this function will be removed. + */ +goog.events.getTotalListenerCount = function() { + return goog.events.listenerCountEstimate_; +}; + + +/** + * Dispatches an event (or event like object) and calls all listeners + * listening for events of this type. The type of the event is decided by the + * type property on the event object. + * + * If any of the listeners returns false OR calls preventDefault then this + * function will return false. If one of the capture listeners calls + * stopPropagation, then the bubble listeners won't fire. + * + * @param {goog.events.Listenable} src The event target. + * @param {goog.events.EventLike} e Event object. + * @return {boolean} If anyone called preventDefault on the event object (or + * if any of the handlers returns false) this will also return false. + * If there are no handlers, or if all handlers return true, this returns + * true. + */ +goog.events.dispatchEvent = function(src, e) { + goog.asserts.assert( + goog.events.Listenable.isImplementedBy(src), + 'Can not use goog.events.dispatchEvent with ' + + 'non-goog.events.Listenable instance.'); + return src.dispatchEvent(e); +}; + + +/** + * Installs exception protection for the browser event entry point using the + * given error handler. + * + * @param {goog.debug.ErrorHandler} errorHandler Error handler with which to + * protect the entry point. + */ +goog.events.protectBrowserEventEntryPoint = function(errorHandler) { + goog.events.handleBrowserEvent_ = errorHandler.protectEntryPoint( + goog.events.handleBrowserEvent_); +}; + + +/** + * Handles an event and dispatches it to the correct listeners. This + * function is a proxy for the real listener the user specified. + * + * @param {goog.events.Listener} listener The listener object. + * @param {Event=} opt_evt Optional event object that gets passed in via the + * native event handlers. + * @return {boolean} Result of the event handler. + * @this {EventTarget} The object or Element that fired the event. + * @private + */ +goog.events.handleBrowserEvent_ = function(listener, opt_evt) { + if (listener.removed) { + return true; + } + + // Synthesize event propagation if the browser does not support W3C + // event model. + if (!goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) { + var ieEvent = opt_evt || + /** @type {Event} */ (goog.getObjectByName('window.event')); + var evt = new goog.events.BrowserEvent(ieEvent, this); + var retval = true; + + if (goog.events.CAPTURE_SIMULATION_MODE == + goog.events.CaptureSimulationMode.ON) { + // If we have not marked this event yet, we should perform capture + // simulation. + if (!goog.events.isMarkedIeEvent_(ieEvent)) { + goog.events.markIeEvent_(ieEvent); + + var ancestors = []; + for (var parent = evt.currentTarget; parent; + parent = parent.parentNode) { + ancestors.push(parent); + } + + // Fire capture listeners. + var type = listener.type; + for (var i = ancestors.length - 1; !evt.propagationStopped_ && i >= 0; + i--) { + evt.currentTarget = ancestors[i]; + retval &= goog.events.fireListeners_(ancestors[i], type, true, evt); + } + + // Fire bubble listeners. + // + // We can technically rely on IE to perform bubble event + // propagation. However, it turns out that IE fires events in + // opposite order of attachEvent registration, which broke + // some code and tests that rely on the order. (While W3C DOM + // Level 2 Events TR leaves the event ordering unspecified, + // modern browsers and W3C DOM Level 3 Events Working Draft + // actually specify the order as the registration order.) + for (var i = 0; !evt.propagationStopped_ && i < ancestors.length; i++) { + evt.currentTarget = ancestors[i]; + retval &= goog.events.fireListeners_(ancestors[i], type, false, evt); + } + } + } else { + retval = goog.events.fireListener(listener, evt); + } + return retval; + } + + // Otherwise, simply fire the listener. + return goog.events.fireListener( + listener, new goog.events.BrowserEvent(opt_evt, this)); +}; + + +/** + * This is used to mark the IE event object so we do not do the Closure pass + * twice for a bubbling event. + * @param {Event} e The IE browser event. + * @private + */ +goog.events.markIeEvent_ = function(e) { + // Only the keyCode and the returnValue can be changed. We use keyCode for + // non keyboard events. + // event.returnValue is a bit more tricky. It is undefined by default. A + // boolean false prevents the default action. In a window.onbeforeunload and + // the returnValue is non undefined it will be alerted. However, we will only + // modify the returnValue for keyboard events. We can get a problem if non + // closure events sets the keyCode or the returnValue + + var useReturnValue = false; + + if (e.keyCode == 0) { + // We cannot change the keyCode in case that srcElement is input[type=file]. + // We could test that that is the case but that would allocate 3 objects. + // If we use try/catch we will only allocate extra objects in the case of a + // failure. + /** @preserveTry */ + try { + e.keyCode = -1; + return; + } catch (ex) { + useReturnValue = true; + } + } + + if (useReturnValue || + /** @type {boolean|undefined} */ (e.returnValue) == undefined) { + e.returnValue = true; + } +}; + + +/** + * This is used to check if an IE event has already been handled by the Closure + * system so we do not do the Closure pass twice for a bubbling event. + * @param {Event} e The IE browser event. + * @return {boolean} True if the event object has been marked. + * @private + */ +goog.events.isMarkedIeEvent_ = function(e) { + return e.keyCode < 0 || e.returnValue != undefined; +}; + + +/** + * Counter to create unique event ids. + * @private {number} + */ +goog.events.uniqueIdCounter_ = 0; + + +/** + * Creates a unique event id. + * + * @param {string} identifier The identifier. + * @return {string} A unique identifier. + * @idGenerator + */ +goog.events.getUniqueId = function(identifier) { + return identifier + '_' + goog.events.uniqueIdCounter_++; +}; + + +/** + * @param {EventTarget} src The source object. + * @return {goog.events.ListenerMap} A listener map for the given + * source object, or null if none exists. + * @private + */ +goog.events.getListenerMap_ = function(src) { + var listenerMap = src[goog.events.LISTENER_MAP_PROP_]; + // IE serializes the property as well (e.g. when serializing outer + // HTML). So we must check that the value is of the correct type. + return listenerMap instanceof goog.events.ListenerMap ? listenerMap : null; +}; + + +/** + * Expando property for listener function wrapper for Object with + * handleEvent. + * @const + * @private + */ +goog.events.LISTENER_WRAPPER_PROP_ = '__closure_events_fn_' + + ((Math.random() * 1e9) >>> 0); + + +/** + * @param {Object|Function} listener The listener function or an + * object that contains handleEvent method. + * @return {!Function} Either the original function or a function that + * calls obj.handleEvent. If the same listener is passed to this + * function more than once, the same function is guaranteed to be + * returned. + */ +goog.events.wrapListener = function(listener) { + goog.asserts.assert(listener, 'Listener can not be null.'); + + if (goog.isFunction(listener)) { + return listener; + } + + goog.asserts.assert( + listener.handleEvent, 'An object listener must have handleEvent method.'); + if (!listener[goog.events.LISTENER_WRAPPER_PROP_]) { + listener[goog.events.LISTENER_WRAPPER_PROP_] = + function(e) { return listener.handleEvent(e); }; + } + return listener[goog.events.LISTENER_WRAPPER_PROP_]; +}; + + +// Register the browser event handler as an entry point, so that +// it can be monitored for exception handling, etc. +goog.debug.entryPointRegistry.register( + /** + * @param {function(!Function): !Function} transformer The transforming + * function. + */ + function(transformer) { + goog.events.handleBrowserEvent_ = transformer( + goog.events.handleBrowserEvent_); + }); diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventtarget.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventtarget.js new file mode 100644 index 0000000..172c412 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventtarget.js @@ -0,0 +1,395 @@ +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A disposable implementation of a custom + * listenable/event target. See also: documentation for + * {@code goog.events.Listenable}. + * + * @author arv@google.com (Erik Arvidsson) [Original implementation] + * @author pupius@google.com (Daniel Pupius) [Port to use goog.events] + * @see ../demos/eventtarget.html + * @see goog.events.Listenable + */ + +goog.provide('goog.events.EventTarget'); + +goog.require('goog.Disposable'); +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.Event'); +goog.require('goog.events.Listenable'); +goog.require('goog.events.ListenerMap'); +goog.require('goog.object'); + + + +/** + * An implementation of {@code goog.events.Listenable} with full W3C + * EventTarget-like support (capture/bubble mechanism, stopping event + * propagation, preventing default actions). + * + * You may subclass this class to turn your class into a Listenable. + * + * Unless propagation is stopped, an event dispatched by an + * EventTarget will bubble to the parent returned by + * {@code getParentEventTarget}. To set the parent, call + * {@code setParentEventTarget}. Subclasses that don't support + * changing the parent can override the setter to throw an error. + * + * Example usage: + * <pre> + * var source = new goog.events.EventTarget(); + * function handleEvent(e) { + * alert('Type: ' + e.type + '; Target: ' + e.target); + * } + * source.listen('foo', handleEvent); + * // Or: goog.events.listen(source, 'foo', handleEvent); + * ... + * source.dispatchEvent('foo'); // will call handleEvent + * ... + * source.unlisten('foo', handleEvent); + * // Or: goog.events.unlisten(source, 'foo', handleEvent); + * </pre> + * + * @constructor + * @extends {goog.Disposable} + * @implements {goog.events.Listenable} + */ +goog.events.EventTarget = function() { + goog.Disposable.call(this); + + /** + * Maps of event type to an array of listeners. + * @private {!goog.events.ListenerMap} + */ + this.eventTargetListeners_ = new goog.events.ListenerMap(this); + + /** + * The object to use for event.target. Useful when mixing in an + * EventTarget to another object. + * @private {!Object} + */ + this.actualEventTarget_ = this; + + /** + * Parent event target, used during event bubbling. + * + * TODO(user): Change this to goog.events.Listenable. This + * currently breaks people who expect getParentEventTarget to return + * goog.events.EventTarget. + * + * @private {goog.events.EventTarget} + */ + this.parentEventTarget_ = null; +}; +goog.inherits(goog.events.EventTarget, goog.Disposable); +goog.events.Listenable.addImplementation(goog.events.EventTarget); + + +/** + * An artificial cap on the number of ancestors you can have. This is mainly + * for loop detection. + * @const {number} + * @private + */ +goog.events.EventTarget.MAX_ANCESTORS_ = 1000; + + +/** + * Returns the parent of this event target to use for bubbling. + * + * @return {goog.events.EventTarget} The parent EventTarget or null if + * there is no parent. + * @override + */ +goog.events.EventTarget.prototype.getParentEventTarget = function() { + return this.parentEventTarget_; +}; + + +/** + * Sets the parent of this event target to use for capture/bubble + * mechanism. + * @param {goog.events.EventTarget} parent Parent listenable (null if none). + */ +goog.events.EventTarget.prototype.setParentEventTarget = function(parent) { + this.parentEventTarget_ = parent; +}; + + +/** + * Adds an event listener to the event target. The same handler can only be + * added once per the type. Even if you add the same handler multiple times + * using the same type then it will only be called once when the event is + * dispatched. + * + * @param {string} type The type of the event to listen for. + * @param {function(?):?|{handleEvent:function(?):?}|null} handler The function + * to handle the event. The handler can also be an object that implements + * the handleEvent method which takes the event object as argument. + * @param {boolean=} opt_capture In DOM-compliant browsers, this determines + * whether the listener is fired during the capture or bubble phase + * of the event. + * @param {Object=} opt_handlerScope Object in whose scope to call + * the listener. + * @deprecated Use {@code #listen} instead, when possible. Otherwise, use + * {@code goog.events.listen} if you are passing Object + * (instead of Function) as handler. + */ +goog.events.EventTarget.prototype.addEventListener = function( + type, handler, opt_capture, opt_handlerScope) { + goog.events.listen(this, type, handler, opt_capture, opt_handlerScope); +}; + + +/** + * Removes an event listener from the event target. The handler must be the + * same object as the one added. If the handler has not been added then + * nothing is done. + * + * @param {string} type The type of the event to listen for. + * @param {function(?):?|{handleEvent:function(?):?}|null} handler The function + * to handle the event. The handler can also be an object that implements + * the handleEvent method which takes the event object as argument. + * @param {boolean=} opt_capture In DOM-compliant browsers, this determines + * whether the listener is fired during the capture or bubble phase + * of the event. + * @param {Object=} opt_handlerScope Object in whose scope to call + * the listener. + * @deprecated Use {@code #unlisten} instead, when possible. Otherwise, use + * {@code goog.events.unlisten} if you are passing Object + * (instead of Function) as handler. + */ +goog.events.EventTarget.prototype.removeEventListener = function( + type, handler, opt_capture, opt_handlerScope) { + goog.events.unlisten(this, type, handler, opt_capture, opt_handlerScope); +}; + + +/** @override */ +goog.events.EventTarget.prototype.dispatchEvent = function(e) { + this.assertInitialized_(); + + var ancestorsTree, ancestor = this.getParentEventTarget(); + if (ancestor) { + ancestorsTree = []; + var ancestorCount = 1; + for (; ancestor; ancestor = ancestor.getParentEventTarget()) { + ancestorsTree.push(ancestor); + goog.asserts.assert( + (++ancestorCount < goog.events.EventTarget.MAX_ANCESTORS_), + 'infinite loop'); + } + } + + return goog.events.EventTarget.dispatchEventInternal_( + this.actualEventTarget_, e, ancestorsTree); +}; + + +/** + * Removes listeners from this object. Classes that extend EventTarget may + * need to override this method in order to remove references to DOM Elements + * and additional listeners. + * @override + */ +goog.events.EventTarget.prototype.disposeInternal = function() { + goog.events.EventTarget.superClass_.disposeInternal.call(this); + + this.removeAllListeners(); + this.parentEventTarget_ = null; +}; + + +/** @override */ +goog.events.EventTarget.prototype.listen = function( + type, listener, opt_useCapture, opt_listenerScope) { + this.assertInitialized_(); + return this.eventTargetListeners_.add( + String(type), listener, false /* callOnce */, opt_useCapture, + opt_listenerScope); +}; + + +/** @override */ +goog.events.EventTarget.prototype.listenOnce = function( + type, listener, opt_useCapture, opt_listenerScope) { + return this.eventTargetListeners_.add( + String(type), listener, true /* callOnce */, opt_useCapture, + opt_listenerScope); +}; + + +/** @override */ +goog.events.EventTarget.prototype.unlisten = function( + type, listener, opt_useCapture, opt_listenerScope) { + return this.eventTargetListeners_.remove( + String(type), listener, opt_useCapture, opt_listenerScope); +}; + + +/** @override */ +goog.events.EventTarget.prototype.unlistenByKey = function(key) { + return this.eventTargetListeners_.removeByKey(key); +}; + + +/** @override */ +goog.events.EventTarget.prototype.removeAllListeners = function(opt_type) { + // TODO(user): Previously, removeAllListeners can be called on + // uninitialized EventTarget, so we preserve that behavior. We + // should remove this when usages that rely on that fact are purged. + if (!this.eventTargetListeners_) { + return 0; + } + return this.eventTargetListeners_.removeAll(opt_type); +}; + + +/** @override */ +goog.events.EventTarget.prototype.fireListeners = function( + type, capture, eventObject) { + // TODO(user): Original code avoids array creation when there + // is no listener, so we do the same. If this optimization turns + // out to be not required, we can replace this with + // getListeners(type, capture) instead, which is simpler. + var listenerArray = this.eventTargetListeners_.listeners[String(type)]; + if (!listenerArray) { + return true; + } + listenerArray = listenerArray.concat(); + + var rv = true; + for (var i = 0; i < listenerArray.length; ++i) { + var listener = listenerArray[i]; + // We might not have a listener if the listener was removed. + if (listener && !listener.removed && listener.capture == capture) { + var listenerFn = listener.listener; + var listenerHandler = listener.handler || listener.src; + + if (listener.callOnce) { + this.unlistenByKey(listener); + } + rv = listenerFn.call(listenerHandler, eventObject) !== false && rv; + } + } + + return rv && eventObject.returnValue_ != false; +}; + + +/** @override */ +goog.events.EventTarget.prototype.getListeners = function(type, capture) { + return this.eventTargetListeners_.getListeners(String(type), capture); +}; + + +/** @override */ +goog.events.EventTarget.prototype.getListener = function( + type, listener, capture, opt_listenerScope) { + return this.eventTargetListeners_.getListener( + String(type), listener, capture, opt_listenerScope); +}; + + +/** @override */ +goog.events.EventTarget.prototype.hasListener = function( + opt_type, opt_capture) { + var id = goog.isDef(opt_type) ? String(opt_type) : undefined; + return this.eventTargetListeners_.hasListener(id, opt_capture); +}; + + +/** + * Sets the target to be used for {@code event.target} when firing + * event. Mainly used for testing. For example, see + * {@code goog.testing.events.mixinListenable}. + * @param {!Object} target The target. + */ +goog.events.EventTarget.prototype.setTargetForTesting = function(target) { + this.actualEventTarget_ = target; +}; + + +/** + * Asserts that the event target instance is initialized properly. + * @private + */ +goog.events.EventTarget.prototype.assertInitialized_ = function() { + goog.asserts.assert( + this.eventTargetListeners_, + 'Event target is not initialized. Did you call the superclass ' + + '(goog.events.EventTarget) constructor?'); +}; + + +/** + * Dispatches the given event on the ancestorsTree. + * + * @param {!Object} target The target to dispatch on. + * @param {goog.events.Event|Object|string} e The event object. + * @param {Array.<goog.events.Listenable>=} opt_ancestorsTree The ancestors + * tree of the target, in reverse order from the closest ancestor + * to the root event target. May be null if the target has no ancestor. + * @return {boolean} If anyone called preventDefault on the event object (or + * if any of the listeners returns false) this will also return false. + * @private + */ +goog.events.EventTarget.dispatchEventInternal_ = function( + target, e, opt_ancestorsTree) { + var type = e.type || /** @type {string} */ (e); + + // If accepting a string or object, create a custom event object so that + // preventDefault and stopPropagation work with the event. + if (goog.isString(e)) { + e = new goog.events.Event(e, target); + } else if (!(e instanceof goog.events.Event)) { + var oldEvent = e; + e = new goog.events.Event(type, target); + goog.object.extend(e, oldEvent); + } else { + e.target = e.target || target; + } + + var rv = true, currentTarget; + + // Executes all capture listeners on the ancestors, if any. + if (opt_ancestorsTree) { + for (var i = opt_ancestorsTree.length - 1; !e.propagationStopped_ && i >= 0; + i--) { + currentTarget = e.currentTarget = opt_ancestorsTree[i]; + rv = currentTarget.fireListeners(type, true, e) && rv; + } + } + + // Executes capture and bubble listeners on the target. + if (!e.propagationStopped_) { + currentTarget = e.currentTarget = target; + rv = currentTarget.fireListeners(type, true, e) && rv; + if (!e.propagationStopped_) { + rv = currentTarget.fireListeners(type, false, e) && rv; + } + } + + // Executes all bubble listeners on the ancestors, if any. + if (opt_ancestorsTree) { + for (i = 0; !e.propagationStopped_ && i < opt_ancestorsTree.length; i++) { + currentTarget = e.currentTarget = opt_ancestorsTree[i]; + rv = currentTarget.fireListeners(type, false, e) && rv; + } + } + + return rv; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventtype.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventtype.js new file mode 100644 index 0000000..37ee9c8 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventtype.js @@ -0,0 +1,226 @@ +// Copyright 2010 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Event Types. + * + * @author arv@google.com (Erik Arvidsson) + * @author mirkov@google.com (Mirko Visontai) + */ + + +goog.provide('goog.events.EventType'); + +goog.require('goog.userAgent'); + + +/** + * Returns a prefixed event name for the current browser. + * @param {string} eventName The name of the event. + * @return {string} The prefixed event name. + * @suppress {missingRequire|missingProvide} + * @private + */ +goog.events.getVendorPrefixedName_ = function(eventName) { + return goog.userAgent.WEBKIT ? 'webkit' + eventName : + (goog.userAgent.OPERA ? 'o' + eventName.toLowerCase() : + eventName.toLowerCase()); +}; + + +/** + * Constants for event names. + * @enum {string} + */ +goog.events.EventType = { + // Mouse events + CLICK: 'click', + RIGHTCLICK: 'rightclick', + DBLCLICK: 'dblclick', + MOUSEDOWN: 'mousedown', + MOUSEUP: 'mouseup', + MOUSEOVER: 'mouseover', + MOUSEOUT: 'mouseout', + MOUSEMOVE: 'mousemove', + MOUSEENTER: 'mouseenter', + MOUSELEAVE: 'mouseleave', + // Select start is non-standard. + // See http://msdn.microsoft.com/en-us/library/ie/ms536969(v=vs.85).aspx. + SELECTSTART: 'selectstart', // IE, Safari, Chrome + + // Key events + KEYPRESS: 'keypress', + KEYDOWN: 'keydown', + KEYUP: 'keyup', + + // Focus + BLUR: 'blur', + FOCUS: 'focus', + DEACTIVATE: 'deactivate', // IE only + // NOTE: The following two events are not stable in cross-browser usage. + // WebKit and Opera implement DOMFocusIn/Out. + // IE implements focusin/out. + // Gecko implements neither see bug at + // https://bugzilla.mozilla.org/show_bug.cgi?id=396927. + // The DOM Events Level 3 Draft deprecates DOMFocusIn in favor of focusin: + // http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html + // You can use FOCUS in Capture phase until implementations converge. + FOCUSIN: goog.userAgent.IE ? 'focusin' : 'DOMFocusIn', + FOCUSOUT: goog.userAgent.IE ? 'focusout' : 'DOMFocusOut', + + // Forms + CHANGE: 'change', + SELECT: 'select', + SUBMIT: 'submit', + INPUT: 'input', + PROPERTYCHANGE: 'propertychange', // IE only + + // Drag and drop + DRAGSTART: 'dragstart', + DRAG: 'drag', + DRAGENTER: 'dragenter', + DRAGOVER: 'dragover', + DRAGLEAVE: 'dragleave', + DROP: 'drop', + DRAGEND: 'dragend', + + // WebKit touch events. + TOUCHSTART: 'touchstart', + TOUCHMOVE: 'touchmove', + TOUCHEND: 'touchend', + TOUCHCANCEL: 'touchcancel', + + // Misc + BEFOREUNLOAD: 'beforeunload', + CONSOLEMESSAGE: 'consolemessage', + CONTEXTMENU: 'contextmenu', + DOMCONTENTLOADED: 'DOMContentLoaded', + ERROR: 'error', + HELP: 'help', + LOAD: 'load', + LOSECAPTURE: 'losecapture', + ORIENTATIONCHANGE: 'orientationchange', + READYSTATECHANGE: 'readystatechange', + RESIZE: 'resize', + SCROLL: 'scroll', + UNLOAD: 'unload', + + // HTML 5 History events + // See http://www.w3.org/TR/html5/history.html#event-definitions + HASHCHANGE: 'hashchange', + PAGEHIDE: 'pagehide', + PAGESHOW: 'pageshow', + POPSTATE: 'popstate', + + // Copy and Paste + // Support is limited. Make sure it works on your favorite browser + // before using. + // http://www.quirksmode.org/dom/events/cutcopypaste.html + COPY: 'copy', + PASTE: 'paste', + CUT: 'cut', + BEFORECOPY: 'beforecopy', + BEFORECUT: 'beforecut', + BEFOREPASTE: 'beforepaste', + + // HTML5 online/offline events. + // http://www.w3.org/TR/offline-webapps/#related + ONLINE: 'online', + OFFLINE: 'offline', + + // HTML 5 worker events + MESSAGE: 'message', + CONNECT: 'connect', + + // CSS animation events. + /** @suppress {missingRequire} */ + ANIMATIONSTART: goog.events.getVendorPrefixedName_('AnimationStart'), + /** @suppress {missingRequire} */ + ANIMATIONEND: goog.events.getVendorPrefixedName_('AnimationEnd'), + /** @suppress {missingRequire} */ + ANIMATIONITERATION: goog.events.getVendorPrefixedName_('AnimationIteration'), + + // CSS transition events. Based on the browser support described at: + // https://developer.mozilla.org/en/css/css_transitions#Browser_compatibility + /** @suppress {missingRequire} */ + TRANSITIONEND: goog.events.getVendorPrefixedName_('TransitionEnd'), + + // W3C Pointer Events + // http://www.w3.org/TR/pointerevents/ + POINTERDOWN: 'pointerdown', + POINTERUP: 'pointerup', + POINTERCANCEL: 'pointercancel', + POINTERMOVE: 'pointermove', + POINTEROVER: 'pointerover', + POINTEROUT: 'pointerout', + POINTERENTER: 'pointerenter', + POINTERLEAVE: 'pointerleave', + GOTPOINTERCAPTURE: 'gotpointercapture', + LOSTPOINTERCAPTURE: 'lostpointercapture', + + // IE specific events. + // See http://msdn.microsoft.com/en-us/library/ie/hh772103(v=vs.85).aspx + // Note: these events will be supplanted in IE11. + MSGESTURECHANGE: 'MSGestureChange', + MSGESTUREEND: 'MSGestureEnd', + MSGESTUREHOLD: 'MSGestureHold', + MSGESTURESTART: 'MSGestureStart', + MSGESTURETAP: 'MSGestureTap', + MSGOTPOINTERCAPTURE: 'MSGotPointerCapture', + MSINERTIASTART: 'MSInertiaStart', + MSLOSTPOINTERCAPTURE: 'MSLostPointerCapture', + MSPOINTERCANCEL: 'MSPointerCancel', + MSPOINTERDOWN: 'MSPointerDown', + MSPOINTERENTER: 'MSPointerEnter', + MSPOINTERHOVER: 'MSPointerHover', + MSPOINTERLEAVE: 'MSPointerLeave', + MSPOINTERMOVE: 'MSPointerMove', + MSPOINTEROUT: 'MSPointerOut', + MSPOINTEROVER: 'MSPointerOver', + MSPOINTERUP: 'MSPointerUp', + + // Native IMEs/input tools events. + TEXTINPUT: 'textinput', + COMPOSITIONSTART: 'compositionstart', + COMPOSITIONUPDATE: 'compositionupdate', + COMPOSITIONEND: 'compositionend', + + // Webview tag events + // See http://developer.chrome.com/dev/apps/webview_tag.html + EXIT: 'exit', + LOADABORT: 'loadabort', + LOADCOMMIT: 'loadcommit', + LOADREDIRECT: 'loadredirect', + LOADSTART: 'loadstart', + LOADSTOP: 'loadstop', + RESPONSIVE: 'responsive', + SIZECHANGED: 'sizechanged', + UNRESPONSIVE: 'unresponsive', + + // HTML5 Page Visibility API. See details at + // {@code goog.labs.dom.PageVisibilityMonitor}. + VISIBILITYCHANGE: 'visibilitychange', + + // LocalStorage event. + STORAGE: 'storage', + + // DOM Level 2 mutation events (deprecated). + DOMSUBTREEMODIFIED: 'DOMSubtreeModified', + DOMNODEINSERTED: 'DOMNodeInserted', + DOMNODEREMOVED: 'DOMNodeRemoved', + DOMNODEREMOVEDFROMDOCUMENT: 'DOMNodeRemovedFromDocument', + DOMNODEINSERTEDINTODOCUMENT: 'DOMNodeInsertedIntoDocument', + DOMATTRMODIFIED: 'DOMAttrModified', + DOMCHARACTERDATAMODIFIED: 'DOMCharacterDataModified' +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventwrapper.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventwrapper.js new file mode 100644 index 0000000..c556bc1 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/eventwrapper.js @@ -0,0 +1,68 @@ +// Copyright 2009 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Definition of the goog.events.EventWrapper interface. + * + * @author eae@google.com (Emil A Eklund) + */ + +goog.provide('goog.events.EventWrapper'); + + + +/** + * Interface for event wrappers. + * @interface + */ +goog.events.EventWrapper = function() { +}; + + +/** + * Adds an event listener using the wrapper on a DOM Node or an object that has + * implemented {@link goog.events.EventTarget}. A listener can only be added + * once to an object. + * + * @param {EventTarget|goog.events.EventTarget} src The node to listen to + * events on. + * @param {Function|Object} listener Callback method, or an object with a + * handleEvent function. + * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to + * false). + * @param {Object=} opt_scope Element in whose scope to call the listener. + * @param {goog.events.EventHandler=} opt_eventHandler Event handler to add + * listener to. + */ +goog.events.EventWrapper.prototype.listen = function(src, listener, opt_capt, + opt_scope, opt_eventHandler) { +}; + + +/** + * Removes an event listener added using goog.events.EventWrapper.listen. + * + * @param {EventTarget|goog.events.EventTarget} src The node to remove listener + * from. + * @param {Function|Object} listener Callback method, or an object with a + * handleEvent function. + * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to + * false). + * @param {Object=} opt_scope Element in whose scope to call the listener. + * @param {goog.events.EventHandler=} opt_eventHandler Event handler to remove + * listener from. + */ +goog.events.EventWrapper.prototype.unlisten = function(src, listener, opt_capt, + opt_scope, opt_eventHandler) { +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/keycodes.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/keycodes.js new file mode 100644 index 0000000..d0ad82c --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/keycodes.js @@ -0,0 +1,415 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Constant declarations for common key codes. + * + * @author eae@google.com (Emil A Eklund) + * @see ../demos/keyhandler.html + */ + +goog.provide('goog.events.KeyCodes'); + +goog.require('goog.userAgent'); + + +/** + * Key codes for common characters. + * + * This list is not localized and therefore some of the key codes are not + * correct for non US keyboard layouts. See comments below. + * + * @enum {number} + */ +goog.events.KeyCodes = { + WIN_KEY_FF_LINUX: 0, + MAC_ENTER: 3, + BACKSPACE: 8, + TAB: 9, + NUM_CENTER: 12, // NUMLOCK on FF/Safari Mac + ENTER: 13, + SHIFT: 16, + CTRL: 17, + ALT: 18, + PAUSE: 19, + CAPS_LOCK: 20, + ESC: 27, + SPACE: 32, + PAGE_UP: 33, // also NUM_NORTH_EAST + PAGE_DOWN: 34, // also NUM_SOUTH_EAST + END: 35, // also NUM_SOUTH_WEST + HOME: 36, // also NUM_NORTH_WEST + LEFT: 37, // also NUM_WEST + UP: 38, // also NUM_NORTH + RIGHT: 39, // also NUM_EAST + DOWN: 40, // also NUM_SOUTH + PRINT_SCREEN: 44, + INSERT: 45, // also NUM_INSERT + DELETE: 46, // also NUM_DELETE + ZERO: 48, + ONE: 49, + TWO: 50, + THREE: 51, + FOUR: 52, + FIVE: 53, + SIX: 54, + SEVEN: 55, + EIGHT: 56, + NINE: 57, + FF_SEMICOLON: 59, // Firefox (Gecko) fires this for semicolon instead of 186 + FF_EQUALS: 61, // Firefox (Gecko) fires this for equals instead of 187 + FF_DASH: 173, // Firefox (Gecko) fires this for dash instead of 189 + QUESTION_MARK: 63, // needs localization + A: 65, + B: 66, + C: 67, + D: 68, + E: 69, + F: 70, + G: 71, + H: 72, + I: 73, + J: 74, + K: 75, + L: 76, + M: 77, + N: 78, + O: 79, + P: 80, + Q: 81, + R: 82, + S: 83, + T: 84, + U: 85, + V: 86, + W: 87, + X: 88, + Y: 89, + Z: 90, + META: 91, // WIN_KEY_LEFT + WIN_KEY_RIGHT: 92, + CONTEXT_MENU: 93, + NUM_ZERO: 96, + NUM_ONE: 97, + NUM_TWO: 98, + NUM_THREE: 99, + NUM_FOUR: 100, + NUM_FIVE: 101, + NUM_SIX: 102, + NUM_SEVEN: 103, + NUM_EIGHT: 104, + NUM_NINE: 105, + NUM_MULTIPLY: 106, + NUM_PLUS: 107, + NUM_MINUS: 109, + NUM_PERIOD: 110, + NUM_DIVISION: 111, + F1: 112, + F2: 113, + F3: 114, + F4: 115, + F5: 116, + F6: 117, + F7: 118, + F8: 119, + F9: 120, + F10: 121, + F11: 122, + F12: 123, + NUMLOCK: 144, + SCROLL_LOCK: 145, + + // OS-specific media keys like volume controls and browser controls. + FIRST_MEDIA_KEY: 166, + LAST_MEDIA_KEY: 183, + + SEMICOLON: 186, // needs localization + DASH: 189, // needs localization + EQUALS: 187, // needs localization + COMMA: 188, // needs localization + PERIOD: 190, // needs localization + SLASH: 191, // needs localization + APOSTROPHE: 192, // needs localization + TILDE: 192, // needs localization + SINGLE_QUOTE: 222, // needs localization + OPEN_SQUARE_BRACKET: 219, // needs localization + BACKSLASH: 220, // needs localization + CLOSE_SQUARE_BRACKET: 221, // needs localization + WIN_KEY: 224, + MAC_FF_META: 224, // Firefox (Gecko) fires this for the meta key instead of 91 + MAC_WK_CMD_LEFT: 91, // WebKit Left Command key fired, same as META + MAC_WK_CMD_RIGHT: 93, // WebKit Right Command key fired, different from META + WIN_IME: 229, + + // We've seen users whose machines fire this keycode at regular one + // second intervals. The common thread among these users is that + // they're all using Dell Inspiron laptops, so we suspect that this + // indicates a hardware/bios problem. + // http://en.community.dell.com/support-forums/laptop/f/3518/p/19285957/19523128.aspx + PHANTOM: 255 +}; + + +/** + * Returns true if the event contains a text modifying key. + * @param {goog.events.BrowserEvent} e A key event. + * @return {boolean} Whether it's a text modifying key. + */ +goog.events.KeyCodes.isTextModifyingKeyEvent = function(e) { + if (e.altKey && !e.ctrlKey || + e.metaKey || + // Function keys don't generate text + e.keyCode >= goog.events.KeyCodes.F1 && + e.keyCode <= goog.events.KeyCodes.F12) { + return false; + } + + // The following keys are quite harmless, even in combination with + // CTRL, ALT or SHIFT. + switch (e.keyCode) { + case goog.events.KeyCodes.ALT: + case goog.events.KeyCodes.CAPS_LOCK: + case goog.events.KeyCodes.CONTEXT_MENU: + case goog.events.KeyCodes.CTRL: + case goog.events.KeyCodes.DOWN: + case goog.events.KeyCodes.END: + case goog.events.KeyCodes.ESC: + case goog.events.KeyCodes.HOME: + case goog.events.KeyCodes.INSERT: + case goog.events.KeyCodes.LEFT: + case goog.events.KeyCodes.MAC_FF_META: + case goog.events.KeyCodes.META: + case goog.events.KeyCodes.NUMLOCK: + case goog.events.KeyCodes.NUM_CENTER: + case goog.events.KeyCodes.PAGE_DOWN: + case goog.events.KeyCodes.PAGE_UP: + case goog.events.KeyCodes.PAUSE: + case goog.events.KeyCodes.PHANTOM: + case goog.events.KeyCodes.PRINT_SCREEN: + case goog.events.KeyCodes.RIGHT: + case goog.events.KeyCodes.SCROLL_LOCK: + case goog.events.KeyCodes.SHIFT: + case goog.events.KeyCodes.UP: + case goog.events.KeyCodes.WIN_KEY: + case goog.events.KeyCodes.WIN_KEY_RIGHT: + return false; + case goog.events.KeyCodes.WIN_KEY_FF_LINUX: + return !goog.userAgent.GECKO; + default: + return e.keyCode < goog.events.KeyCodes.FIRST_MEDIA_KEY || + e.keyCode > goog.events.KeyCodes.LAST_MEDIA_KEY; + } +}; + + +/** + * Returns true if the key fires a keypress event in the current browser. + * + * Accoridng to MSDN [1] IE only fires keypress events for the following keys: + * - Letters: A - Z (uppercase and lowercase) + * - Numerals: 0 - 9 + * - Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~ + * - System: ESC, SPACEBAR, ENTER + * + * That's not entirely correct though, for instance there's no distinction + * between upper and lower case letters. + * + * [1] http://msdn2.microsoft.com/en-us/library/ms536939(VS.85).aspx) + * + * Safari is similar to IE, but does not fire keypress for ESC. + * + * Additionally, IE6 does not fire keydown or keypress events for letters when + * the control or alt keys are held down and the shift key is not. IE7 does + * fire keydown in these cases, though, but not keypress. + * + * @param {number} keyCode A key code. + * @param {number=} opt_heldKeyCode Key code of a currently-held key. + * @param {boolean=} opt_shiftKey Whether the shift key is held down. + * @param {boolean=} opt_ctrlKey Whether the control key is held down. + * @param {boolean=} opt_altKey Whether the alt key is held down. + * @return {boolean} Whether it's a key that fires a keypress event. + */ +goog.events.KeyCodes.firesKeyPressEvent = function(keyCode, opt_heldKeyCode, + opt_shiftKey, opt_ctrlKey, opt_altKey) { + if (!goog.userAgent.IE && + !(goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('525'))) { + return true; + } + + if (goog.userAgent.MAC && opt_altKey) { + return goog.events.KeyCodes.isCharacterKey(keyCode); + } + + // Alt but not AltGr which is represented as Alt+Ctrl. + if (opt_altKey && !opt_ctrlKey) { + return false; + } + + // Saves Ctrl or Alt + key for IE and WebKit 525+, which won't fire keypress. + // Non-IE browsers and WebKit prior to 525 won't get this far so no need to + // check the user agent. + if (goog.isNumber(opt_heldKeyCode)) { + opt_heldKeyCode = goog.events.KeyCodes.normalizeKeyCode(opt_heldKeyCode); + } + if (!opt_shiftKey && + (opt_heldKeyCode == goog.events.KeyCodes.CTRL || + opt_heldKeyCode == goog.events.KeyCodes.ALT || + goog.userAgent.MAC && + opt_heldKeyCode == goog.events.KeyCodes.META)) { + return false; + } + + // Some keys with Ctrl/Shift do not issue keypress in WEBKIT. + if (goog.userAgent.WEBKIT && opt_ctrlKey && opt_shiftKey) { + switch (keyCode) { + case goog.events.KeyCodes.BACKSLASH: + case goog.events.KeyCodes.OPEN_SQUARE_BRACKET: + case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET: + case goog.events.KeyCodes.TILDE: + case goog.events.KeyCodes.SEMICOLON: + case goog.events.KeyCodes.DASH: + case goog.events.KeyCodes.EQUALS: + case goog.events.KeyCodes.COMMA: + case goog.events.KeyCodes.PERIOD: + case goog.events.KeyCodes.SLASH: + case goog.events.KeyCodes.APOSTROPHE: + case goog.events.KeyCodes.SINGLE_QUOTE: + return false; + } + } + + // When Ctrl+<somekey> is held in IE, it only fires a keypress once, but it + // continues to fire keydown events as the event repeats. + if (goog.userAgent.IE && opt_ctrlKey && opt_heldKeyCode == keyCode) { + return false; + } + + switch (keyCode) { + case goog.events.KeyCodes.ENTER: + return true; + case goog.events.KeyCodes.ESC: + return !goog.userAgent.WEBKIT; + } + + return goog.events.KeyCodes.isCharacterKey(keyCode); +}; + + +/** + * Returns true if the key produces a character. + * This does not cover characters on non-US keyboards (Russian, Hebrew, etc.). + * + * @param {number} keyCode A key code. + * @return {boolean} Whether it's a character key. + */ +goog.events.KeyCodes.isCharacterKey = function(keyCode) { + if (keyCode >= goog.events.KeyCodes.ZERO && + keyCode <= goog.events.KeyCodes.NINE) { + return true; + } + + if (keyCode >= goog.events.KeyCodes.NUM_ZERO && + keyCode <= goog.events.KeyCodes.NUM_MULTIPLY) { + return true; + } + + if (keyCode >= goog.events.KeyCodes.A && + keyCode <= goog.events.KeyCodes.Z) { + return true; + } + + // Safari sends zero key code for non-latin characters. + if (goog.userAgent.WEBKIT && keyCode == 0) { + return true; + } + + switch (keyCode) { + case goog.events.KeyCodes.SPACE: + case goog.events.KeyCodes.QUESTION_MARK: + case goog.events.KeyCodes.NUM_PLUS: + case goog.events.KeyCodes.NUM_MINUS: + case goog.events.KeyCodes.NUM_PERIOD: + case goog.events.KeyCodes.NUM_DIVISION: + case goog.events.KeyCodes.SEMICOLON: + case goog.events.KeyCodes.FF_SEMICOLON: + case goog.events.KeyCodes.DASH: + case goog.events.KeyCodes.EQUALS: + case goog.events.KeyCodes.FF_EQUALS: + case goog.events.KeyCodes.COMMA: + case goog.events.KeyCodes.PERIOD: + case goog.events.KeyCodes.SLASH: + case goog.events.KeyCodes.APOSTROPHE: + case goog.events.KeyCodes.SINGLE_QUOTE: + case goog.events.KeyCodes.OPEN_SQUARE_BRACKET: + case goog.events.KeyCodes.BACKSLASH: + case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET: + return true; + default: + return false; + } +}; + + +/** + * Normalizes key codes from OS/Browser-specific value to the general one. + * @param {number} keyCode The native key code. + * @return {number} The normalized key code. + */ +goog.events.KeyCodes.normalizeKeyCode = function(keyCode) { + if (goog.userAgent.GECKO) { + return goog.events.KeyCodes.normalizeGeckoKeyCode(keyCode); + } else if (goog.userAgent.MAC && goog.userAgent.WEBKIT) { + return goog.events.KeyCodes.normalizeMacWebKitKeyCode(keyCode); + } else { + return keyCode; + } +}; + + +/** + * Normalizes key codes from their Gecko-specific value to the general one. + * @param {number} keyCode The native key code. + * @return {number} The normalized key code. + */ +goog.events.KeyCodes.normalizeGeckoKeyCode = function(keyCode) { + switch (keyCode) { + case goog.events.KeyCodes.FF_EQUALS: + return goog.events.KeyCodes.EQUALS; + case goog.events.KeyCodes.FF_SEMICOLON: + return goog.events.KeyCodes.SEMICOLON; + case goog.events.KeyCodes.FF_DASH: + return goog.events.KeyCodes.DASH; + case goog.events.KeyCodes.MAC_FF_META: + return goog.events.KeyCodes.META; + case goog.events.KeyCodes.WIN_KEY_FF_LINUX: + return goog.events.KeyCodes.WIN_KEY; + default: + return keyCode; + } +}; + + +/** + * Normalizes key codes from their Mac WebKit-specific value to the general one. + * @param {number} keyCode The native key code. + * @return {number} The normalized key code. + */ +goog.events.KeyCodes.normalizeMacWebKitKeyCode = function(keyCode) { + switch (keyCode) { + case goog.events.KeyCodes.MAC_WK_CMD_RIGHT: // 93 + return goog.events.KeyCodes.META; // 91 + default: + return keyCode; + } +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/keyhandler.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/keyhandler.js new file mode 100644 index 0000000..6a7662f --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/keyhandler.js @@ -0,0 +1,556 @@ +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview This file contains a class for working with keyboard events + * that repeat consistently across browsers and platforms. It also unifies the + * key code so that it is the same in all browsers and platforms. + * + * Different web browsers have very different keyboard event handling. Most + * importantly is that only certain browsers repeat keydown events: + * IE, Opera, FF/Win32, and Safari 3 repeat keydown events. + * FF/Mac and Safari 2 do not. + * + * For the purposes of this code, "Safari 3" means WebKit 525+, when WebKit + * decided that they should try to match IE's key handling behavior. + * Safari 3.0.4, which shipped with Leopard (WebKit 523), has the + * Safari 2 behavior. + * + * Firefox, Safari, Opera prevent on keypress + * + * IE prevents on keydown + * + * Firefox does not fire keypress for shift, ctrl, alt + * Firefox does fire keydown for shift, ctrl, alt, meta + * Firefox does not repeat keydown for shift, ctrl, alt, meta + * + * Firefox does not fire keypress for up and down in an input + * + * Opera fires keypress for shift, ctrl, alt, meta + * Opera does not repeat keypress for shift, ctrl, alt, meta + * + * Safari 2 and 3 do not fire keypress for shift, ctrl, alt + * Safari 2 does not fire keydown for shift, ctrl, alt + * Safari 3 *does* fire keydown for shift, ctrl, alt + * + * IE provides the keycode for keyup/down events and the charcode (in the + * keycode field) for keypress. + * + * Mozilla provides the keycode for keyup/down and the charcode for keypress + * unless it's a non text modifying key in which case the keycode is provided. + * + * Safari 3 provides the keycode and charcode for all events. + * + * Opera provides the keycode for keyup/down event and either the charcode or + * the keycode (in the keycode field) for keypress events. + * + * Firefox x11 doesn't fire keydown events if a another key is already held down + * until the first key is released. This can cause a key event to be fired with + * a keyCode for the first key and a charCode for the second key. + * + * Safari in keypress + * + * charCode keyCode which + * ENTER: 13 13 13 + * F1: 63236 63236 63236 + * F8: 63243 63243 63243 + * ... + * p: 112 112 112 + * P: 80 80 80 + * + * Firefox, keypress: + * + * charCode keyCode which + * ENTER: 0 13 13 + * F1: 0 112 0 + * F8: 0 119 0 + * ... + * p: 112 0 112 + * P: 80 0 80 + * + * Opera, Mac+Win32, keypress: + * + * charCode keyCode which + * ENTER: undefined 13 13 + * F1: undefined 112 0 + * F8: undefined 119 0 + * ... + * p: undefined 112 112 + * P: undefined 80 80 + * + * IE7, keydown + * + * charCode keyCode which + * ENTER: undefined 13 undefined + * F1: undefined 112 undefined + * F8: undefined 119 undefined + * ... + * p: undefined 80 undefined + * P: undefined 80 undefined + * + * @author arv@google.com (Erik Arvidsson) + * @author eae@google.com (Emil A Eklund) + * @see ../demos/keyhandler.html + */ + +goog.provide('goog.events.KeyEvent'); +goog.provide('goog.events.KeyHandler'); +goog.provide('goog.events.KeyHandler.EventType'); + +goog.require('goog.events'); +goog.require('goog.events.BrowserEvent'); +goog.require('goog.events.EventTarget'); +goog.require('goog.events.EventType'); +goog.require('goog.events.KeyCodes'); +goog.require('goog.userAgent'); + + + +/** + * A wrapper around an element that you want to listen to keyboard events on. + * @param {Element|Document=} opt_element The element or document to listen on. + * @param {boolean=} opt_capture Whether to listen for browser events in + * capture phase (defaults to false). + * @constructor + * @extends {goog.events.EventTarget} + * @final + */ +goog.events.KeyHandler = function(opt_element, opt_capture) { + goog.events.EventTarget.call(this); + + if (opt_element) { + this.attach(opt_element, opt_capture); + } +}; +goog.inherits(goog.events.KeyHandler, goog.events.EventTarget); + + +/** + * This is the element that we will listen to the real keyboard events on. + * @type {Element|Document|null} + * @private + */ +goog.events.KeyHandler.prototype.element_ = null; + + +/** + * The key for the key press listener. + * @type {goog.events.Key} + * @private + */ +goog.events.KeyHandler.prototype.keyPressKey_ = null; + + +/** + * The key for the key down listener. + * @type {goog.events.Key} + * @private + */ +goog.events.KeyHandler.prototype.keyDownKey_ = null; + + +/** + * The key for the key up listener. + * @type {goog.events.Key} + * @private + */ +goog.events.KeyHandler.prototype.keyUpKey_ = null; + + +/** + * Used to detect keyboard repeat events. + * @private + * @type {number} + */ +goog.events.KeyHandler.prototype.lastKey_ = -1; + + +/** + * Keycode recorded for key down events. As most browsers don't report the + * keycode in the key press event we need to record it in the key down phase. + * @private + * @type {number} + */ +goog.events.KeyHandler.prototype.keyCode_ = -1; + + +/** + * Alt key recorded for key down events. FF on Mac does not report the alt key + * flag in the key press event, we need to record it in the key down phase. + * @type {boolean} + * @private + */ +goog.events.KeyHandler.prototype.altKey_ = false; + + +/** + * Enum type for the events fired by the key handler + * @enum {string} + */ +goog.events.KeyHandler.EventType = { + KEY: 'key' +}; + + +/** + * An enumeration of key codes that Safari 2 does incorrectly + * @type {Object} + * @private + */ +goog.events.KeyHandler.safariKey_ = { + '3': goog.events.KeyCodes.ENTER, // 13 + '12': goog.events.KeyCodes.NUMLOCK, // 144 + '63232': goog.events.KeyCodes.UP, // 38 + '63233': goog.events.KeyCodes.DOWN, // 40 + '63234': goog.events.KeyCodes.LEFT, // 37 + '63235': goog.events.KeyCodes.RIGHT, // 39 + '63236': goog.events.KeyCodes.F1, // 112 + '63237': goog.events.KeyCodes.F2, // 113 + '63238': goog.events.KeyCodes.F3, // 114 + '63239': goog.events.KeyCodes.F4, // 115 + '63240': goog.events.KeyCodes.F5, // 116 + '63241': goog.events.KeyCodes.F6, // 117 + '63242': goog.events.KeyCodes.F7, // 118 + '63243': goog.events.KeyCodes.F8, // 119 + '63244': goog.events.KeyCodes.F9, // 120 + '63245': goog.events.KeyCodes.F10, // 121 + '63246': goog.events.KeyCodes.F11, // 122 + '63247': goog.events.KeyCodes.F12, // 123 + '63248': goog.events.KeyCodes.PRINT_SCREEN, // 44 + '63272': goog.events.KeyCodes.DELETE, // 46 + '63273': goog.events.KeyCodes.HOME, // 36 + '63275': goog.events.KeyCodes.END, // 35 + '63276': goog.events.KeyCodes.PAGE_UP, // 33 + '63277': goog.events.KeyCodes.PAGE_DOWN, // 34 + '63289': goog.events.KeyCodes.NUMLOCK, // 144 + '63302': goog.events.KeyCodes.INSERT // 45 +}; + + +/** + * An enumeration of key identifiers currently part of the W3C draft for DOM3 + * and their mappings to keyCodes. + * http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set + * This is currently supported in Safari and should be platform independent. + * @type {Object} + * @private + */ +goog.events.KeyHandler.keyIdentifier_ = { + 'Up': goog.events.KeyCodes.UP, // 38 + 'Down': goog.events.KeyCodes.DOWN, // 40 + 'Left': goog.events.KeyCodes.LEFT, // 37 + 'Right': goog.events.KeyCodes.RIGHT, // 39 + 'Enter': goog.events.KeyCodes.ENTER, // 13 + 'F1': goog.events.KeyCodes.F1, // 112 + 'F2': goog.events.KeyCodes.F2, // 113 + 'F3': goog.events.KeyCodes.F3, // 114 + 'F4': goog.events.KeyCodes.F4, // 115 + 'F5': goog.events.KeyCodes.F5, // 116 + 'F6': goog.events.KeyCodes.F6, // 117 + 'F7': goog.events.KeyCodes.F7, // 118 + 'F8': goog.events.KeyCodes.F8, // 119 + 'F9': goog.events.KeyCodes.F9, // 120 + 'F10': goog.events.KeyCodes.F10, // 121 + 'F11': goog.events.KeyCodes.F11, // 122 + 'F12': goog.events.KeyCodes.F12, // 123 + 'U+007F': goog.events.KeyCodes.DELETE, // 46 + 'Home': goog.events.KeyCodes.HOME, // 36 + 'End': goog.events.KeyCodes.END, // 35 + 'PageUp': goog.events.KeyCodes.PAGE_UP, // 33 + 'PageDown': goog.events.KeyCodes.PAGE_DOWN, // 34 + 'Insert': goog.events.KeyCodes.INSERT // 45 +}; + + +/** + * If true, the KeyEvent fires on keydown. Otherwise, it fires on keypress. + * + * @type {boolean} + * @private + */ +goog.events.KeyHandler.USES_KEYDOWN_ = goog.userAgent.IE || + goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('525'); + + +/** + * If true, the alt key flag is saved during the key down and reused when + * handling the key press. FF on Mac does not set the alt flag in the key press + * event. + * @type {boolean} + * @private + */ +goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_ = goog.userAgent.MAC && + goog.userAgent.GECKO; + + +/** + * Records the keycode for browsers that only returns the keycode for key up/ + * down events. For browser/key combinations that doesn't trigger a key pressed + * event it also fires the patched key event. + * @param {goog.events.BrowserEvent} e The key down event. + * @private + */ +goog.events.KeyHandler.prototype.handleKeyDown_ = function(e) { + // Ctrl-Tab and Alt-Tab can cause the focus to be moved to another window + // before we've caught a key-up event. If the last-key was one of these we + // reset the state. + if (goog.userAgent.WEBKIT) { + if (this.lastKey_ == goog.events.KeyCodes.CTRL && !e.ctrlKey || + this.lastKey_ == goog.events.KeyCodes.ALT && !e.altKey || + goog.userAgent.MAC && + this.lastKey_ == goog.events.KeyCodes.META && !e.metaKey) { + this.lastKey_ = -1; + this.keyCode_ = -1; + } + } + + if (this.lastKey_ == -1) { + if (e.ctrlKey && e.keyCode != goog.events.KeyCodes.CTRL) { + this.lastKey_ = goog.events.KeyCodes.CTRL; + } else if (e.altKey && e.keyCode != goog.events.KeyCodes.ALT) { + this.lastKey_ = goog.events.KeyCodes.ALT; + } else if (e.metaKey && e.keyCode != goog.events.KeyCodes.META) { + this.lastKey_ = goog.events.KeyCodes.META; + } + } + + if (goog.events.KeyHandler.USES_KEYDOWN_ && + !goog.events.KeyCodes.firesKeyPressEvent(e.keyCode, + this.lastKey_, e.shiftKey, e.ctrlKey, e.altKey)) { + this.handleEvent(e); + } else { + this.keyCode_ = goog.events.KeyCodes.normalizeKeyCode(e.keyCode); + if (goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_) { + this.altKey_ = e.altKey; + } + } +}; + + +/** + * Resets the stored previous values. Needed to be called for webkit which will + * not generate a key up for meta key operations. This should only be called + * when having finished with repeat key possiblities. + */ +goog.events.KeyHandler.prototype.resetState = function() { + this.lastKey_ = -1; + this.keyCode_ = -1; +}; + + +/** + * Clears the stored previous key value, resetting the key repeat status. Uses + * -1 because the Safari 3 Windows beta reports 0 for certain keys (like Home + * and End.) + * @param {goog.events.BrowserEvent} e The keyup event. + * @private + */ +goog.events.KeyHandler.prototype.handleKeyup_ = function(e) { + this.resetState(); + this.altKey_ = e.altKey; +}; + + +/** + * Handles the events on the element. + * @param {goog.events.BrowserEvent} e The keyboard event sent from the + * browser. + */ +goog.events.KeyHandler.prototype.handleEvent = function(e) { + var be = e.getBrowserEvent(); + var keyCode, charCode; + var altKey = be.altKey; + + // IE reports the character code in the keyCode field for keypress events. + // There are two exceptions however, Enter and Escape. + if (goog.userAgent.IE && e.type == goog.events.EventType.KEYPRESS) { + keyCode = this.keyCode_; + charCode = keyCode != goog.events.KeyCodes.ENTER && + keyCode != goog.events.KeyCodes.ESC ? + be.keyCode : 0; + + // Safari reports the character code in the keyCode field for keypress + // events but also has a charCode field. + } else if (goog.userAgent.WEBKIT && + e.type == goog.events.EventType.KEYPRESS) { + keyCode = this.keyCode_; + charCode = be.charCode >= 0 && be.charCode < 63232 && + goog.events.KeyCodes.isCharacterKey(keyCode) ? + be.charCode : 0; + + // Opera reports the keycode or the character code in the keyCode field. + } else if (goog.userAgent.OPERA) { + keyCode = this.keyCode_; + charCode = goog.events.KeyCodes.isCharacterKey(keyCode) ? + be.keyCode : 0; + + // Mozilla reports the character code in the charCode field. + } else { + keyCode = be.keyCode || this.keyCode_; + charCode = be.charCode || 0; + if (goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_) { + altKey = this.altKey_; + } + // On the Mac, shift-/ triggers a question mark char code and no key code + // (normalized to WIN_KEY), so we synthesize the latter. + if (goog.userAgent.MAC && + charCode == goog.events.KeyCodes.QUESTION_MARK && + keyCode == goog.events.KeyCodes.WIN_KEY) { + keyCode = goog.events.KeyCodes.SLASH; + } + } + + keyCode = goog.events.KeyCodes.normalizeKeyCode(keyCode); + var key = keyCode; + var keyIdentifier = be.keyIdentifier; + + // Correct the key value for certain browser-specific quirks. + if (keyCode) { + if (keyCode >= 63232 && keyCode in goog.events.KeyHandler.safariKey_) { + // NOTE(nicksantos): Safari 3 has fixed this problem, + // this is only needed for Safari 2. + key = goog.events.KeyHandler.safariKey_[keyCode]; + } else { + + // Safari returns 25 for Shift+Tab instead of 9. + if (keyCode == 25 && e.shiftKey) { + key = 9; + } + } + } else if (keyIdentifier && + keyIdentifier in goog.events.KeyHandler.keyIdentifier_) { + // This is needed for Safari Windows because it currently doesn't give a + // keyCode/which for non printable keys. + key = goog.events.KeyHandler.keyIdentifier_[keyIdentifier]; + } + + // If we get the same keycode as a keydown/keypress without having seen a + // keyup event, then this event was caused by key repeat. + var repeat = key == this.lastKey_; + this.lastKey_ = key; + + var event = new goog.events.KeyEvent(key, charCode, repeat, be); + event.altKey = altKey; + this.dispatchEvent(event); +}; + + +/** + * Returns the element listened on for the real keyboard events. + * @return {Element|Document|null} The element listened on for the real + * keyboard events. + */ +goog.events.KeyHandler.prototype.getElement = function() { + return this.element_; +}; + + +/** + * Adds the proper key event listeners to the element. + * @param {Element|Document} element The element to listen on. + * @param {boolean=} opt_capture Whether to listen for browser events in + * capture phase (defaults to false). + */ +goog.events.KeyHandler.prototype.attach = function(element, opt_capture) { + if (this.keyUpKey_) { + this.detach(); + } + + this.element_ = element; + + this.keyPressKey_ = goog.events.listen(this.element_, + goog.events.EventType.KEYPRESS, + this, + opt_capture); + + // Most browsers (Safari 2 being the notable exception) doesn't include the + // keyCode in keypress events (IE has the char code in the keyCode field and + // Mozilla only included the keyCode if there's no charCode). Thus we have to + // listen for keydown to capture the keycode. + this.keyDownKey_ = goog.events.listen(this.element_, + goog.events.EventType.KEYDOWN, + this.handleKeyDown_, + opt_capture, + this); + + + this.keyUpKey_ = goog.events.listen(this.element_, + goog.events.EventType.KEYUP, + this.handleKeyup_, + opt_capture, + this); +}; + + +/** + * Removes the listeners that may exist. + */ +goog.events.KeyHandler.prototype.detach = function() { + if (this.keyPressKey_) { + goog.events.unlistenByKey(this.keyPressKey_); + goog.events.unlistenByKey(this.keyDownKey_); + goog.events.unlistenByKey(this.keyUpKey_); + this.keyPressKey_ = null; + this.keyDownKey_ = null; + this.keyUpKey_ = null; + } + this.element_ = null; + this.lastKey_ = -1; + this.keyCode_ = -1; +}; + + +/** @override */ +goog.events.KeyHandler.prototype.disposeInternal = function() { + goog.events.KeyHandler.superClass_.disposeInternal.call(this); + this.detach(); +}; + + + +/** + * This class is used for the goog.events.KeyHandler.EventType.KEY event and + * it overrides the key code with the fixed key code. + * @param {number} keyCode The adjusted key code. + * @param {number} charCode The unicode character code. + * @param {boolean} repeat Whether this event was generated by keyboard repeat. + * @param {Event} browserEvent Browser event object. + * @constructor + * @extends {goog.events.BrowserEvent} + * @final + */ +goog.events.KeyEvent = function(keyCode, charCode, repeat, browserEvent) { + goog.events.BrowserEvent.call(this, browserEvent); + this.type = goog.events.KeyHandler.EventType.KEY; + + /** + * Keycode of key press. + * @type {number} + */ + this.keyCode = keyCode; + + /** + * Unicode character code. + * @type {number} + */ + this.charCode = charCode; + + /** + * True if this event was generated by keyboard auto-repeat (i.e., the user is + * holding the key down.) + * @type {boolean} + */ + this.repeat = repeat; +}; +goog.inherits(goog.events.KeyEvent, goog.events.BrowserEvent); diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/listenable.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/listenable.js new file mode 100644 index 0000000..6483f26 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/listenable.js @@ -0,0 +1,334 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview An interface for a listenable JavaScript object. + */ + +goog.provide('goog.events.Listenable'); +goog.provide('goog.events.ListenableKey'); + +/** @suppress {extraRequire} */ +goog.require('goog.events.EventId'); + + + +/** + * A listenable interface. A listenable is an object with the ability + * to dispatch/broadcast events to "event listeners" registered via + * listen/listenOnce. + * + * The interface allows for an event propagation mechanism similar + * to one offered by native browser event targets, such as + * capture/bubble mechanism, stopping propagation, and preventing + * default actions. Capture/bubble mechanism depends on the ancestor + * tree constructed via {@code #getParentEventTarget}; this tree + * must be directed acyclic graph. The meaning of default action(s) + * in preventDefault is specific to a particular use case. + * + * Implementations that do not support capture/bubble or can not have + * a parent listenable can simply not implement any ability to set the + * parent listenable (and have {@code #getParentEventTarget} return + * null). + * + * Implementation of this class can be used with or independently from + * goog.events. + * + * Implementation must call {@code #addImplementation(implClass)}. + * + * @interface + * @see goog.events + * @see http://www.w3.org/TR/DOM-Level-2-Events/events.html + */ +goog.events.Listenable = function() {}; + + +/** + * An expando property to indicate that an object implements + * goog.events.Listenable. + * + * See addImplementation/isImplementedBy. + * + * @type {string} + * @const + */ +goog.events.Listenable.IMPLEMENTED_BY_PROP = + 'closure_listenable_' + ((Math.random() * 1e6) | 0); + + +/** + * Marks a given class (constructor) as an implementation of + * Listenable, do that we can query that fact at runtime. The class + * must have already implemented the interface. + * @param {!Function} cls The class constructor. The corresponding + * class must have already implemented the interface. + */ +goog.events.Listenable.addImplementation = function(cls) { + cls.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP] = true; +}; + + +/** + * @param {Object} obj The object to check. + * @return {boolean} Whether a given instance implements Listenable. The + * class/superclass of the instance must call addImplementation. + */ +goog.events.Listenable.isImplementedBy = function(obj) { + return !!(obj && obj[goog.events.Listenable.IMPLEMENTED_BY_PROP]); +}; + + +/** + * Adds an event listener. A listener can only be added once to an + * object and if it is added again the key for the listener is + * returned. Note that if the existing listener is a one-off listener + * (registered via listenOnce), it will no longer be a one-off + * listener after a call to listen(). + * + * @param {string|!goog.events.EventId.<EVENTOBJ>} type The event type id. + * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback + * method. + * @param {boolean=} opt_useCapture Whether to fire in capture phase + * (defaults to false). + * @param {SCOPE=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {goog.events.ListenableKey} Unique key for the listener. + * @template SCOPE,EVENTOBJ + */ +goog.events.Listenable.prototype.listen; + + +/** + * Adds an event listener that is removed automatically after the + * listener fired once. + * + * If an existing listener already exists, listenOnce will do + * nothing. In particular, if the listener was previously registered + * via listen(), listenOnce() will not turn the listener into a + * one-off listener. Similarly, if there is already an existing + * one-off listener, listenOnce does not modify the listeners (it is + * still a once listener). + * + * @param {string|!goog.events.EventId.<EVENTOBJ>} type The event type id. + * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback + * method. + * @param {boolean=} opt_useCapture Whether to fire in capture phase + * (defaults to false). + * @param {SCOPE=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {goog.events.ListenableKey} Unique key for the listener. + * @template SCOPE,EVENTOBJ + */ +goog.events.Listenable.prototype.listenOnce; + + +/** + * Removes an event listener which was added with listen() or listenOnce(). + * + * @param {string|!goog.events.EventId.<EVENTOBJ>} type The event type id. + * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback + * method. + * @param {boolean=} opt_useCapture Whether to fire in capture phase + * (defaults to false). + * @param {SCOPE=} opt_listenerScope Object in whose scope to call + * the listener. + * @return {boolean} Whether any listener was removed. + * @template SCOPE,EVENTOBJ + */ +goog.events.Listenable.prototype.unlisten; + + +/** + * Removes an event listener which was added with listen() by the key + * returned by listen(). + * + * @param {goog.events.ListenableKey} key The key returned by + * listen() or listenOnce(). + * @return {boolean} Whether any listener was removed. + */ +goog.events.Listenable.prototype.unlistenByKey; + + +/** + * Dispatches an event (or event like object) and calls all listeners + * listening for events of this type. The type of the event is decided by the + * type property on the event object. + * + * If any of the listeners returns false OR calls preventDefault then this + * function will return false. If one of the capture listeners calls + * stopPropagation, then the bubble listeners won't fire. + * + * @param {goog.events.EventLike} e Event object. + * @return {boolean} If anyone called preventDefault on the event object (or + * if any of the listeners returns false) this will also return false. + */ +goog.events.Listenable.prototype.dispatchEvent; + + +/** + * Removes all listeners from this listenable. If type is specified, + * it will only remove listeners of the particular type. otherwise all + * registered listeners will be removed. + * + * @param {string=} opt_type Type of event to remove, default is to + * remove all types. + * @return {number} Number of listeners removed. + */ +goog.events.Listenable.prototype.removeAllListeners; + + +/** + * Returns the parent of this event target to use for capture/bubble + * mechanism. + * + * NOTE(user): The name reflects the original implementation of + * custom event target ({@code goog.events.EventTarget}). We decided + * that changing the name is not worth it. + * + * @return {goog.events.Listenable} The parent EventTarget or null if + * there is no parent. + */ +goog.events.Listenable.prototype.getParentEventTarget; + + +/** + * Fires all registered listeners in this listenable for the given + * type and capture mode, passing them the given eventObject. This + * does not perform actual capture/bubble. Only implementors of the + * interface should be using this. + * + * @param {string|!goog.events.EventId.<EVENTOBJ>} type The type of the + * listeners to fire. + * @param {boolean} capture The capture mode of the listeners to fire. + * @param {EVENTOBJ} eventObject The event object to fire. + * @return {boolean} Whether all listeners succeeded without + * attempting to prevent default behavior. If any listener returns + * false or called goog.events.Event#preventDefault, this returns + * false. + * @template EVENTOBJ + */ +goog.events.Listenable.prototype.fireListeners; + + +/** + * Gets all listeners in this listenable for the given type and + * capture mode. + * + * @param {string|!goog.events.EventId} type The type of the listeners to fire. + * @param {boolean} capture The capture mode of the listeners to fire. + * @return {!Array.<goog.events.ListenableKey>} An array of registered + * listeners. + * @template EVENTOBJ + */ +goog.events.Listenable.prototype.getListeners; + + +/** + * Gets the goog.events.ListenableKey for the event or null if no such + * listener is in use. + * + * @param {string|!goog.events.EventId.<EVENTOBJ>} type The name of the event + * without the 'on' prefix. + * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener The + * listener function to get. + * @param {boolean} capture Whether the listener is a capturing listener. + * @param {SCOPE=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {goog.events.ListenableKey} the found listener or null if not found. + * @template SCOPE,EVENTOBJ + */ +goog.events.Listenable.prototype.getListener; + + +/** + * Whether there is any active listeners matching the specified + * signature. If either the type or capture parameters are + * unspecified, the function will match on the remaining criteria. + * + * @param {string|!goog.events.EventId.<EVENTOBJ>=} opt_type Event type. + * @param {boolean=} opt_capture Whether to check for capture or bubble + * listeners. + * @return {boolean} Whether there is any active listeners matching + * the requested type and/or capture phase. + * @template EVENTOBJ + */ +goog.events.Listenable.prototype.hasListener; + + + +/** + * An interface that describes a single registered listener. + * @interface + */ +goog.events.ListenableKey = function() {}; + + +/** + * Counter used to create a unique key + * @type {number} + * @private + */ +goog.events.ListenableKey.counter_ = 0; + + +/** + * Reserves a key to be used for ListenableKey#key field. + * @return {number} A number to be used to fill ListenableKey#key + * field. + */ +goog.events.ListenableKey.reserveKey = function() { + return ++goog.events.ListenableKey.counter_; +}; + + +/** + * The source event target. + * @type {!(Object|goog.events.Listenable|goog.events.EventTarget)} + */ +goog.events.ListenableKey.prototype.src; + + +/** + * The event type the listener is listening to. + * @type {string} + */ +goog.events.ListenableKey.prototype.type; + + +/** + * The listener function. + * @type {function(?):?|{handleEvent:function(?):?}|null} + */ +goog.events.ListenableKey.prototype.listener; + + +/** + * Whether the listener works on capture phase. + * @type {boolean} + */ +goog.events.ListenableKey.prototype.capture; + + +/** + * The 'this' object for the listener function's scope. + * @type {Object} + */ +goog.events.ListenableKey.prototype.handler; + + +/** + * A globally unique number to identify the key. + * @type {number} + */ +goog.events.ListenableKey.prototype.key; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/listener.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/listener.js new file mode 100644 index 0000000..60c7370 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/listener.js @@ -0,0 +1,131 @@ +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Listener object. + * @see ../demos/events.html + */ + +goog.provide('goog.events.Listener'); + +goog.require('goog.events.ListenableKey'); + + + +/** + * Simple class that stores information about a listener + * @param {!Function} listener Callback function. + * @param {Function} proxy Wrapper for the listener that patches the event. + * @param {EventTarget|goog.events.Listenable} src Source object for + * the event. + * @param {string} type Event type. + * @param {boolean} capture Whether in capture or bubble phase. + * @param {Object=} opt_handler Object in whose context to execute the callback. + * @implements {goog.events.ListenableKey} + * @constructor + */ +goog.events.Listener = function( + listener, proxy, src, type, capture, opt_handler) { + if (goog.events.Listener.ENABLE_MONITORING) { + this.creationStack = new Error().stack; + } + + /** + * Callback function. + * @type {Function} + */ + this.listener = listener; + + /** + * A wrapper over the original listener. This is used solely to + * handle native browser events (it is used to simulate the capture + * phase and to patch the event object). + * @type {Function} + */ + this.proxy = proxy; + + /** + * Object or node that callback is listening to + * @type {EventTarget|goog.events.Listenable} + */ + this.src = src; + + /** + * The event type. + * @const {string} + */ + this.type = type; + + /** + * Whether the listener is being called in the capture or bubble phase + * @const {boolean} + */ + this.capture = !!capture; + + /** + * Optional object whose context to execute the listener in + * @type {Object|undefined} + */ + this.handler = opt_handler; + + /** + * The key of the listener. + * @const {number} + * @override + */ + this.key = goog.events.ListenableKey.reserveKey(); + + /** + * Whether to remove the listener after it has been called. + * @type {boolean} + */ + this.callOnce = false; + + /** + * Whether the listener has been removed. + * @type {boolean} + */ + this.removed = false; +}; + + +/** + * @define {boolean} Whether to enable the monitoring of the + * goog.events.Listener instances. Switching on the monitoring is only + * recommended for debugging because it has a significant impact on + * performance and memory usage. If switched off, the monitoring code + * compiles down to 0 bytes. + */ +goog.define('goog.events.Listener.ENABLE_MONITORING', false); + + +/** + * If monitoring the goog.events.Listener instances is enabled, stores the + * creation stack trace of the Disposable instance. + * @type {string} + */ +goog.events.Listener.prototype.creationStack; + + +/** + * Marks this listener as removed. This also remove references held by + * this listener object (such as listener and event source). + */ +goog.events.Listener.prototype.markAsRemoved = function() { + this.removed = true; + this.listener = null; + this.proxy = null; + this.src = null; + this.handler = null; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/events/listenermap.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/listenermap.js new file mode 100644 index 0000000..8930eb6 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/events/listenermap.js @@ -0,0 +1,308 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A map of listeners that provides utility functions to + * deal with listeners on an event target. Used by + * {@code goog.events.EventTarget}. + * + * WARNING: Do not use this class from outside goog.events package. + * + * @visibility {//closure/goog/bin/sizetests:__pkg__} + * @visibility {//closure/goog/events:__pkg__} + * @visibility {//closure/goog/labs/events:__pkg__} + */ + +goog.provide('goog.events.ListenerMap'); + +goog.require('goog.array'); +goog.require('goog.events.Listener'); +goog.require('goog.object'); + + + +/** + * Creates a new listener map. + * @param {EventTarget|goog.events.Listenable} src The src object. + * @constructor + * @final + */ +goog.events.ListenerMap = function(src) { + /** @type {EventTarget|goog.events.Listenable} */ + this.src = src; + + /** + * Maps of event type to an array of listeners. + * @type {Object.<string, !Array.<!goog.events.Listener>>} + */ + this.listeners = {}; + + /** + * The count of types in this map that have registered listeners. + * @private {number} + */ + this.typeCount_ = 0; +}; + + +/** + * @return {number} The count of event types in this map that actually + * have registered listeners. + */ +goog.events.ListenerMap.prototype.getTypeCount = function() { + return this.typeCount_; +}; + + +/** + * @return {number} Total number of registered listeners. + */ +goog.events.ListenerMap.prototype.getListenerCount = function() { + var count = 0; + for (var type in this.listeners) { + count += this.listeners[type].length; + } + return count; +}; + + +/** + * Adds an event listener. A listener can only be added once to an + * object and if it is added again the key for the listener is + * returned. + * + * Note that a one-off listener will not change an existing listener, + * if any. On the other hand a normal listener will change existing + * one-off listener to become a normal listener. + * + * @param {string|!goog.events.EventId} type The listener event type. + * @param {!Function} listener This listener callback method. + * @param {boolean} callOnce Whether the listener is a one-off + * listener. + * @param {boolean=} opt_useCapture The capture mode of the listener. + * @param {Object=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {goog.events.ListenableKey} Unique key for the listener. + */ +goog.events.ListenerMap.prototype.add = function( + type, listener, callOnce, opt_useCapture, opt_listenerScope) { + var typeStr = type.toString(); + var listenerArray = this.listeners[typeStr]; + if (!listenerArray) { + listenerArray = this.listeners[typeStr] = []; + this.typeCount_++; + } + + var listenerObj; + var index = goog.events.ListenerMap.findListenerIndex_( + listenerArray, listener, opt_useCapture, opt_listenerScope); + if (index > -1) { + listenerObj = listenerArray[index]; + if (!callOnce) { + // Ensure that, if there is an existing callOnce listener, it is no + // longer a callOnce listener. + listenerObj.callOnce = false; + } + } else { + listenerObj = new goog.events.Listener( + listener, null, this.src, typeStr, !!opt_useCapture, opt_listenerScope); + listenerObj.callOnce = callOnce; + listenerArray.push(listenerObj); + } + return listenerObj; +}; + + +/** + * Removes a matching listener. + * @param {string|!goog.events.EventId} type The listener event type. + * @param {!Function} listener This listener callback method. + * @param {boolean=} opt_useCapture The capture mode of the listener. + * @param {Object=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {boolean} Whether any listener was removed. + */ +goog.events.ListenerMap.prototype.remove = function( + type, listener, opt_useCapture, opt_listenerScope) { + var typeStr = type.toString(); + if (!(typeStr in this.listeners)) { + return false; + } + + var listenerArray = this.listeners[typeStr]; + var index = goog.events.ListenerMap.findListenerIndex_( + listenerArray, listener, opt_useCapture, opt_listenerScope); + if (index > -1) { + var listenerObj = listenerArray[index]; + listenerObj.markAsRemoved(); + goog.array.removeAt(listenerArray, index); + if (listenerArray.length == 0) { + delete this.listeners[typeStr]; + this.typeCount_--; + } + return true; + } + return false; +}; + + +/** + * Removes the given listener object. + * @param {goog.events.ListenableKey} listener The listener to remove. + * @return {boolean} Whether the listener is removed. + */ +goog.events.ListenerMap.prototype.removeByKey = function(listener) { + var type = listener.type; + if (!(type in this.listeners)) { + return false; + } + + var removed = goog.array.remove(this.listeners[type], listener); + if (removed) { + listener.markAsRemoved(); + if (this.listeners[type].length == 0) { + delete this.listeners[type]; + this.typeCount_--; + } + } + return removed; +}; + + +/** + * Removes all listeners from this map. If opt_type is provided, only + * listeners that match the given type are removed. + * @param {string|!goog.events.EventId=} opt_type Type of event to remove. + * @return {number} Number of listeners removed. + */ +goog.events.ListenerMap.prototype.removeAll = function(opt_type) { + var typeStr = opt_type && opt_type.toString(); + var count = 0; + for (var type in this.listeners) { + if (!typeStr || type == typeStr) { + var listenerArray = this.listeners[type]; + for (var i = 0; i < listenerArray.length; i++) { + ++count; + listenerArray[i].markAsRemoved(); + } + delete this.listeners[type]; + this.typeCount_--; + } + } + return count; +}; + + +/** + * Gets all listeners that match the given type and capture mode. The + * returned array is a copy (but the listener objects are not). + * @param {string|!goog.events.EventId} type The type of the listeners + * to retrieve. + * @param {boolean} capture The capture mode of the listeners to retrieve. + * @return {!Array.<goog.events.ListenableKey>} An array of matching + * listeners. + */ +goog.events.ListenerMap.prototype.getListeners = function(type, capture) { + var listenerArray = this.listeners[type.toString()]; + var rv = []; + if (listenerArray) { + for (var i = 0; i < listenerArray.length; ++i) { + var listenerObj = listenerArray[i]; + if (listenerObj.capture == capture) { + rv.push(listenerObj); + } + } + } + return rv; +}; + + +/** + * Gets the goog.events.ListenableKey for the event or null if no such + * listener is in use. + * + * @param {string|!goog.events.EventId} type The type of the listener + * to retrieve. + * @param {!Function} listener The listener function to get. + * @param {boolean} capture Whether the listener is a capturing listener. + * @param {Object=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {goog.events.ListenableKey} the found listener or null if not found. + */ +goog.events.ListenerMap.prototype.getListener = function( + type, listener, capture, opt_listenerScope) { + var listenerArray = this.listeners[type.toString()]; + var i = -1; + if (listenerArray) { + i = goog.events.ListenerMap.findListenerIndex_( + listenerArray, listener, capture, opt_listenerScope); + } + return i > -1 ? listenerArray[i] : null; +}; + + +/** + * Whether there is a matching listener. If either the type or capture + * parameters are unspecified, the function will match on the + * remaining criteria. + * + * @param {string|!goog.events.EventId=} opt_type The type of the listener. + * @param {boolean=} opt_capture The capture mode of the listener. + * @return {boolean} Whether there is an active listener matching + * the requested type and/or capture phase. + */ +goog.events.ListenerMap.prototype.hasListener = function( + opt_type, opt_capture) { + var hasType = goog.isDef(opt_type); + var typeStr = hasType ? opt_type.toString() : ''; + var hasCapture = goog.isDef(opt_capture); + + return goog.object.some( + this.listeners, function(listenerArray, type) { + for (var i = 0; i < listenerArray.length; ++i) { + if ((!hasType || listenerArray[i].type == typeStr) && + (!hasCapture || listenerArray[i].capture == opt_capture)) { + return true; + } + } + + return false; + }); +}; + + +/** + * Finds the index of a matching goog.events.Listener in the given + * listenerArray. + * @param {!Array.<!goog.events.Listener>} listenerArray Array of listener. + * @param {!Function} listener The listener function. + * @param {boolean=} opt_useCapture The capture flag for the listener. + * @param {Object=} opt_listenerScope The listener scope. + * @return {number} The index of the matching listener within the + * listenerArray. + * @private + */ +goog.events.ListenerMap.findListenerIndex_ = function( + listenerArray, listener, opt_useCapture, opt_listenerScope) { + for (var i = 0; i < listenerArray.length; ++i) { + var listenerObj = listenerArray[i]; + if (!listenerObj.removed && + listenerObj.listener == listener && + listenerObj.capture == !!opt_useCapture && + listenerObj.handler == opt_listenerScope) { + return i; + } + } + return -1; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/functions/functions.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/functions/functions.js new file mode 100644 index 0000000..0c70f01 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/functions/functions.js @@ -0,0 +1,311 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utilities for creating functions. Loosely inspired by the + * java classes: http://goo.gl/GM0Hmu and http://goo.gl/6k7nI8. + * + * @author nicksantos@google.com (Nick Santos) + */ + + +goog.provide('goog.functions'); + + +/** + * Creates a function that always returns the same value. + * @param {T} retValue The value to return. + * @return {function():T} The new function. + * @template T + */ +goog.functions.constant = function(retValue) { + return function() { + return retValue; + }; +}; + + +/** + * Always returns false. + * @type {function(...): boolean} + */ +goog.functions.FALSE = goog.functions.constant(false); + + +/** + * Always returns true. + * @type {function(...): boolean} + */ +goog.functions.TRUE = goog.functions.constant(true); + + +/** + * Always returns NULL. + * @type {function(...): null} + */ +goog.functions.NULL = goog.functions.constant(null); + + +/** + * A simple function that returns the first argument of whatever is passed + * into it. + * @param {T=} opt_returnValue The single value that will be returned. + * @param {...*} var_args Optional trailing arguments. These are ignored. + * @return {T} The first argument passed in, or undefined if nothing was passed. + * @template T + */ +goog.functions.identity = function(opt_returnValue, var_args) { + return opt_returnValue; +}; + + +/** + * Creates a function that always throws an error with the given message. + * @param {string} message The error message. + * @return {!Function} The error-throwing function. + */ +goog.functions.error = function(message) { + return function() { + throw Error(message); + }; +}; + + +/** + * Creates a function that throws the given object. + * @param {*} err An object to be thrown. + * @return {!Function} The error-throwing function. + */ +goog.functions.fail = function(err) { + return function() { + throw err; + } +}; + + +/** + * Given a function, create a function that keeps opt_numArgs arguments and + * silently discards all additional arguments. + * @param {Function} f The original function. + * @param {number=} opt_numArgs The number of arguments to keep. Defaults to 0. + * @return {!Function} A version of f that only keeps the first opt_numArgs + * arguments. + */ +goog.functions.lock = function(f, opt_numArgs) { + opt_numArgs = opt_numArgs || 0; + return function() { + return f.apply(this, Array.prototype.slice.call(arguments, 0, opt_numArgs)); + }; +}; + + +/** + * Creates a function that returns its nth argument. + * @param {number} n The position of the return argument. + * @return {!Function} A new function. + */ +goog.functions.nth = function(n) { + return function() { + return arguments[n]; + }; +}; + + +/** + * Given a function, create a new function that swallows its return value + * and replaces it with a new one. + * @param {Function} f A function. + * @param {T} retValue A new return value. + * @return {function(...[?]):T} A new function. + * @template T + */ +goog.functions.withReturnValue = function(f, retValue) { + return goog.functions.sequence(f, goog.functions.constant(retValue)); +}; + + +/** + * Creates the composition of the functions passed in. + * For example, (goog.functions.compose(f, g))(a) is equivalent to f(g(a)). + * @param {function(...[?]):T} fn The final function. + * @param {...Function} var_args A list of functions. + * @return {function(...[?]):T} The composition of all inputs. + * @template T + */ +goog.functions.compose = function(fn, var_args) { + var functions = arguments; + var length = functions.length; + return function() { + var result; + if (length) { + result = functions[length - 1].apply(this, arguments); + } + + for (var i = length - 2; i >= 0; i--) { + result = functions[i].call(this, result); + } + return result; + }; +}; + + +/** + * Creates a function that calls the functions passed in in sequence, and + * returns the value of the last function. For example, + * (goog.functions.sequence(f, g))(x) is equivalent to f(x),g(x). + * @param {...Function} var_args A list of functions. + * @return {!Function} A function that calls all inputs in sequence. + */ +goog.functions.sequence = function(var_args) { + var functions = arguments; + var length = functions.length; + return function() { + var result; + for (var i = 0; i < length; i++) { + result = functions[i].apply(this, arguments); + } + return result; + }; +}; + + +/** + * Creates a function that returns true if each of its components evaluates + * to true. The components are evaluated in order, and the evaluation will be + * short-circuited as soon as a function returns false. + * For example, (goog.functions.and(f, g))(x) is equivalent to f(x) && g(x). + * @param {...Function} var_args A list of functions. + * @return {function(...[?]):boolean} A function that ANDs its component + * functions. + */ +goog.functions.and = function(var_args) { + var functions = arguments; + var length = functions.length; + return function() { + for (var i = 0; i < length; i++) { + if (!functions[i].apply(this, arguments)) { + return false; + } + } + return true; + }; +}; + + +/** + * Creates a function that returns true if any of its components evaluates + * to true. The components are evaluated in order, and the evaluation will be + * short-circuited as soon as a function returns true. + * For example, (goog.functions.or(f, g))(x) is equivalent to f(x) || g(x). + * @param {...Function} var_args A list of functions. + * @return {function(...[?]):boolean} A function that ORs its component + * functions. + */ +goog.functions.or = function(var_args) { + var functions = arguments; + var length = functions.length; + return function() { + for (var i = 0; i < length; i++) { + if (functions[i].apply(this, arguments)) { + return true; + } + } + return false; + }; +}; + + +/** + * Creates a function that returns the Boolean opposite of a provided function. + * For example, (goog.functions.not(f))(x) is equivalent to !f(x). + * @param {!Function} f The original function. + * @return {function(...[?]):boolean} A function that delegates to f and returns + * opposite. + */ +goog.functions.not = function(f) { + return function() { + return !f.apply(this, arguments); + }; +}; + + +/** + * Generic factory function to construct an object given the constructor + * and the arguments. Intended to be bound to create object factories. + * + * Callers should cast the result to the appropriate type for proper type + * checking by the compiler. + * @param {!Function} constructor The constructor for the Object. + * @param {...*} var_args The arguments to be passed to the constructor. + * @return {!Object} A new instance of the class given in {@code constructor}. + */ +goog.functions.create = function(constructor, var_args) { + /** + * @constructor + * @final + */ + var temp = function() {}; + temp.prototype = constructor.prototype; + + // obj will have constructor's prototype in its chain and + // 'obj instanceof constructor' will be true. + var obj = new temp(); + + // obj is initialized by constructor. + // arguments is only array-like so lacks shift(), but can be used with + // the Array prototype function. + constructor.apply(obj, Array.prototype.slice.call(arguments, 1)); + return obj; +}; + + +/** + * @define {boolean} Whether the return value cache should be used. + * This should only be used to disable caches when testing. + */ +goog.define('goog.functions.CACHE_RETURN_VALUE', true); + + +/** + * Gives a wrapper function that caches the return value of a parameterless + * function when first called. + * + * When called for the first time, the given function is called and its + * return value is cached (thus this is only appropriate for idempotent + * functions). Subsequent calls will return the cached return value. This + * allows the evaluation of expensive functions to be delayed until first used. + * + * To cache the return values of functions with parameters, see goog.memoize. + * + * @param {!function():T} fn A function to lazily evaluate. + * @return {!function():T} A wrapped version the function. + * @template T + */ +goog.functions.cacheReturnValue = function(fn) { + var called = false; + var value; + + return function() { + if (!goog.functions.CACHE_RETURN_VALUE) { + return fn(); + } + + if (!called) { + value = fn(); + called = true; + } + + return value; + } +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/i18n/bidi.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/i18n/bidi.js new file mode 100644 index 0000000..f984c28 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/i18n/bidi.js @@ -0,0 +1,883 @@ +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utility functions for supporting Bidi issues. + */ + + +/** + * Namespace for bidi supporting functions. + */ +goog.provide('goog.i18n.bidi'); +goog.provide('goog.i18n.bidi.Dir'); +goog.provide('goog.i18n.bidi.DirectionalString'); +goog.provide('goog.i18n.bidi.Format'); + + +/** + * @define {boolean} FORCE_RTL forces the {@link goog.i18n.bidi.IS_RTL} constant + * to say that the current locale is a RTL locale. This should only be used + * if you want to override the default behavior for deciding whether the + * current locale is RTL or not. + * + * {@see goog.i18n.bidi.IS_RTL} + */ +goog.define('goog.i18n.bidi.FORCE_RTL', false); + + +/** + * Constant that defines whether or not the current locale is a RTL locale. + * If {@link goog.i18n.bidi.FORCE_RTL} is not true, this constant will default + * to check that {@link goog.LOCALE} is one of a few major RTL locales. + * + * <p>This is designed to be a maximally efficient compile-time constant. For + * example, for the default goog.LOCALE, compiling + * "if (goog.i18n.bidi.IS_RTL) alert('rtl') else {}" should produce no code. It + * is this design consideration that limits the implementation to only + * supporting a few major RTL locales, as opposed to the broader repertoire of + * something like goog.i18n.bidi.isRtlLanguage. + * + * <p>Since this constant refers to the directionality of the locale, it is up + * to the caller to determine if this constant should also be used for the + * direction of the UI. + * + * {@see goog.LOCALE} + * + * @type {boolean} + * + * TODO(user): write a test that checks that this is a compile-time constant. + */ +goog.i18n.bidi.IS_RTL = goog.i18n.bidi.FORCE_RTL || + ( + (goog.LOCALE.substring(0, 2).toLowerCase() == 'ar' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'fa' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'he' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'iw' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'ps' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'sd' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'ug' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'ur' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'yi') && + (goog.LOCALE.length == 2 || + goog.LOCALE.substring(2, 3) == '-' || + goog.LOCALE.substring(2, 3) == '_') + ) || ( + goog.LOCALE.length >= 3 && + goog.LOCALE.substring(0, 3).toLowerCase() == 'ckb' && + (goog.LOCALE.length == 3 || + goog.LOCALE.substring(3, 4) == '-' || + goog.LOCALE.substring(3, 4) == '_') + ); + + +/** + * Unicode formatting characters and directionality string constants. + * @enum {string} + */ +goog.i18n.bidi.Format = { + /** Unicode "Left-To-Right Embedding" (LRE) character. */ + LRE: '\u202A', + /** Unicode "Right-To-Left Embedding" (RLE) character. */ + RLE: '\u202B', + /** Unicode "Pop Directional Formatting" (PDF) character. */ + PDF: '\u202C', + /** Unicode "Left-To-Right Mark" (LRM) character. */ + LRM: '\u200E', + /** Unicode "Right-To-Left Mark" (RLM) character. */ + RLM: '\u200F' +}; + + +/** + * Directionality enum. + * @enum {number} + */ +goog.i18n.bidi.Dir = { + /** + * Left-to-right. + */ + LTR: 1, + + /** + * Right-to-left. + */ + RTL: -1, + + /** + * Neither left-to-right nor right-to-left. + */ + NEUTRAL: 0, + + /** + * A historical misnomer for NEUTRAL. + * @deprecated For "neutral", use NEUTRAL; for "unknown", use null. + */ + UNKNOWN: 0 +}; + + +/** + * 'right' string constant. + * @type {string} + */ +goog.i18n.bidi.RIGHT = 'right'; + + +/** + * 'left' string constant. + * @type {string} + */ +goog.i18n.bidi.LEFT = 'left'; + + +/** + * 'left' if locale is RTL, 'right' if not. + * @type {string} + */ +goog.i18n.bidi.I18N_RIGHT = goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.LEFT : + goog.i18n.bidi.RIGHT; + + +/** + * 'right' if locale is RTL, 'left' if not. + * @type {string} + */ +goog.i18n.bidi.I18N_LEFT = goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.RIGHT : + goog.i18n.bidi.LEFT; + + +/** + * Convert a directionality given in various formats to a goog.i18n.bidi.Dir + * constant. Useful for interaction with different standards of directionality + * representation. + * + * @param {goog.i18n.bidi.Dir|number|boolean|null} givenDir Directionality given + * in one of the following formats: + * 1. A goog.i18n.bidi.Dir constant. + * 2. A number (positive = LTR, negative = RTL, 0 = neutral). + * 3. A boolean (true = RTL, false = LTR). + * 4. A null for unknown directionality. + * @param {boolean=} opt_noNeutral Whether a givenDir of zero or + * goog.i18n.bidi.Dir.NEUTRAL should be treated as null, i.e. unknown, in + * order to preserve legacy behavior. + * @return {?goog.i18n.bidi.Dir} A goog.i18n.bidi.Dir constant matching the + * given directionality. If given null, returns null (i.e. unknown). + */ +goog.i18n.bidi.toDir = function(givenDir, opt_noNeutral) { + if (typeof givenDir == 'number') { + // This includes the non-null goog.i18n.bidi.Dir case. + return givenDir > 0 ? goog.i18n.bidi.Dir.LTR : + givenDir < 0 ? goog.i18n.bidi.Dir.RTL : + opt_noNeutral ? null : goog.i18n.bidi.Dir.NEUTRAL; + } else if (givenDir == null) { + return null; + } else { + // Must be typeof givenDir == 'boolean'. + return givenDir ? goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR; + } +}; + + +/** + * A practical pattern to identify strong LTR characters. This pattern is not + * theoretically correct according to the Unicode standard. It is simplified for + * performance and small code size. + * @type {string} + * @private + */ +goog.i18n.bidi.ltrChars_ = + 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' + + '\u200E\u2C00-\uFB1C\uFE00-\uFE6F\uFEFD-\uFFFF'; + + +/** + * A practical pattern to identify strong RTL character. This pattern is not + * theoretically correct according to the Unicode standard. It is simplified + * for performance and small code size. + * @type {string} + * @private + */ +goog.i18n.bidi.rtlChars_ = '\u0591-\u07FF\u200F\uFB1D-\uFDFF\uFE70-\uFEFC'; + + +/** + * Simplified regular expression for an HTML tag (opening or closing) or an HTML + * escape. We might want to skip over such expressions when estimating the text + * directionality. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.htmlSkipReg_ = /<[^>]*>|&[^;]+;/g; + + +/** + * Returns the input text with spaces instead of HTML tags or HTML escapes, if + * opt_isStripNeeded is true. Else returns the input as is. + * Useful for text directionality estimation. + * Note: the function should not be used in other contexts; it is not 100% + * correct, but rather a good-enough implementation for directionality + * estimation purposes. + * @param {string} str The given string. + * @param {boolean=} opt_isStripNeeded Whether to perform the stripping. + * Default: false (to retain consistency with calling functions). + * @return {string} The given string cleaned of HTML tags / escapes. + * @private + */ +goog.i18n.bidi.stripHtmlIfNeeded_ = function(str, opt_isStripNeeded) { + return opt_isStripNeeded ? str.replace(goog.i18n.bidi.htmlSkipReg_, '') : + str; +}; + + +/** + * Regular expression to check for RTL characters. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.rtlCharReg_ = new RegExp('[' + goog.i18n.bidi.rtlChars_ + ']'); + + +/** + * Regular expression to check for LTR characters. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.ltrCharReg_ = new RegExp('[' + goog.i18n.bidi.ltrChars_ + ']'); + + +/** + * Test whether the given string has any RTL characters in it. + * @param {string} str The given string that need to be tested. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether the string contains RTL characters. + */ +goog.i18n.bidi.hasAnyRtl = function(str, opt_isHtml) { + return goog.i18n.bidi.rtlCharReg_.test(goog.i18n.bidi.stripHtmlIfNeeded_( + str, opt_isHtml)); +}; + + +/** + * Test whether the given string has any RTL characters in it. + * @param {string} str The given string that need to be tested. + * @return {boolean} Whether the string contains RTL characters. + * @deprecated Use hasAnyRtl. + */ +goog.i18n.bidi.hasRtlChar = goog.i18n.bidi.hasAnyRtl; + + +/** + * Test whether the given string has any LTR characters in it. + * @param {string} str The given string that need to be tested. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether the string contains LTR characters. + */ +goog.i18n.bidi.hasAnyLtr = function(str, opt_isHtml) { + return goog.i18n.bidi.ltrCharReg_.test(goog.i18n.bidi.stripHtmlIfNeeded_( + str, opt_isHtml)); +}; + + +/** + * Regular expression pattern to check if the first character in the string + * is LTR. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.ltrRe_ = new RegExp('^[' + goog.i18n.bidi.ltrChars_ + ']'); + + +/** + * Regular expression pattern to check if the first character in the string + * is RTL. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.rtlRe_ = new RegExp('^[' + goog.i18n.bidi.rtlChars_ + ']'); + + +/** + * Check if the first character in the string is RTL or not. + * @param {string} str The given string that need to be tested. + * @return {boolean} Whether the first character in str is an RTL char. + */ +goog.i18n.bidi.isRtlChar = function(str) { + return goog.i18n.bidi.rtlRe_.test(str); +}; + + +/** + * Check if the first character in the string is LTR or not. + * @param {string} str The given string that need to be tested. + * @return {boolean} Whether the first character in str is an LTR char. + */ +goog.i18n.bidi.isLtrChar = function(str) { + return goog.i18n.bidi.ltrRe_.test(str); +}; + + +/** + * Check if the first character in the string is neutral or not. + * @param {string} str The given string that need to be tested. + * @return {boolean} Whether the first character in str is a neutral char. + */ +goog.i18n.bidi.isNeutralChar = function(str) { + return !goog.i18n.bidi.isLtrChar(str) && !goog.i18n.bidi.isRtlChar(str); +}; + + +/** + * Regular expressions to check if a piece of text is of LTR directionality + * on first character with strong directionality. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.ltrDirCheckRe_ = new RegExp( + '^[^' + goog.i18n.bidi.rtlChars_ + ']*[' + goog.i18n.bidi.ltrChars_ + ']'); + + +/** + * Regular expressions to check if a piece of text is of RTL directionality + * on first character with strong directionality. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.rtlDirCheckRe_ = new RegExp( + '^[^' + goog.i18n.bidi.ltrChars_ + ']*[' + goog.i18n.bidi.rtlChars_ + ']'); + + +/** + * Check whether the first strongly directional character (if any) is RTL. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether RTL directionality is detected using the first + * strongly-directional character method. + */ +goog.i18n.bidi.startsWithRtl = function(str, opt_isHtml) { + return goog.i18n.bidi.rtlDirCheckRe_.test(goog.i18n.bidi.stripHtmlIfNeeded_( + str, opt_isHtml)); +}; + + +/** + * Check whether the first strongly directional character (if any) is RTL. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether RTL directionality is detected using the first + * strongly-directional character method. + * @deprecated Use startsWithRtl. + */ +goog.i18n.bidi.isRtlText = goog.i18n.bidi.startsWithRtl; + + +/** + * Check whether the first strongly directional character (if any) is LTR. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether LTR directionality is detected using the first + * strongly-directional character method. + */ +goog.i18n.bidi.startsWithLtr = function(str, opt_isHtml) { + return goog.i18n.bidi.ltrDirCheckRe_.test(goog.i18n.bidi.stripHtmlIfNeeded_( + str, opt_isHtml)); +}; + + +/** + * Check whether the first strongly directional character (if any) is LTR. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether LTR directionality is detected using the first + * strongly-directional character method. + * @deprecated Use startsWithLtr. + */ +goog.i18n.bidi.isLtrText = goog.i18n.bidi.startsWithLtr; + + +/** + * Regular expression to check if a string looks like something that must + * always be LTR even in RTL text, e.g. a URL. When estimating the + * directionality of text containing these, we treat these as weakly LTR, + * like numbers. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.isRequiredLtrRe_ = /^http:\/\/.*/; + + +/** + * Check whether the input string either contains no strongly directional + * characters or looks like a url. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether neutral directionality is detected. + */ +goog.i18n.bidi.isNeutralText = function(str, opt_isHtml) { + str = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml); + return goog.i18n.bidi.isRequiredLtrRe_.test(str) || + !goog.i18n.bidi.hasAnyLtr(str) && !goog.i18n.bidi.hasAnyRtl(str); +}; + + +/** + * Regular expressions to check if the last strongly-directional character in a + * piece of text is LTR. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.ltrExitDirCheckRe_ = new RegExp( + '[' + goog.i18n.bidi.ltrChars_ + '][^' + goog.i18n.bidi.rtlChars_ + ']*$'); + + +/** + * Regular expressions to check if the last strongly-directional character in a + * piece of text is RTL. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.rtlExitDirCheckRe_ = new RegExp( + '[' + goog.i18n.bidi.rtlChars_ + '][^' + goog.i18n.bidi.ltrChars_ + ']*$'); + + +/** + * Check if the exit directionality a piece of text is LTR, i.e. if the last + * strongly-directional character in the string is LTR. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether LTR exit directionality was detected. + */ +goog.i18n.bidi.endsWithLtr = function(str, opt_isHtml) { + return goog.i18n.bidi.ltrExitDirCheckRe_.test( + goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)); +}; + + +/** + * Check if the exit directionality a piece of text is LTR, i.e. if the last + * strongly-directional character in the string is LTR. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether LTR exit directionality was detected. + * @deprecated Use endsWithLtr. + */ +goog.i18n.bidi.isLtrExitText = goog.i18n.bidi.endsWithLtr; + + +/** + * Check if the exit directionality a piece of text is RTL, i.e. if the last + * strongly-directional character in the string is RTL. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether RTL exit directionality was detected. + */ +goog.i18n.bidi.endsWithRtl = function(str, opt_isHtml) { + return goog.i18n.bidi.rtlExitDirCheckRe_.test( + goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)); +}; + + +/** + * Check if the exit directionality a piece of text is RTL, i.e. if the last + * strongly-directional character in the string is RTL. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether RTL exit directionality was detected. + * @deprecated Use endsWithRtl. + */ +goog.i18n.bidi.isRtlExitText = goog.i18n.bidi.endsWithRtl; + + +/** + * A regular expression for matching right-to-left language codes. + * See {@link #isRtlLanguage} for the design. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.rtlLocalesRe_ = new RegExp( + '^(ar|ckb|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|' + + '.*[-_](Arab|Hebr|Thaa|Nkoo|Tfng))' + + '(?!.*[-_](Latn|Cyrl)($|-|_))($|-|_)', + 'i'); + + +/** + * Check if a BCP 47 / III language code indicates an RTL language, i.e. either: + * - a language code explicitly specifying one of the right-to-left scripts, + * e.g. "az-Arab", or<p> + * - a language code specifying one of the languages normally written in a + * right-to-left script, e.g. "fa" (Farsi), except ones explicitly specifying + * Latin or Cyrillic script (which are the usual LTR alternatives).<p> + * The list of right-to-left scripts appears in the 100-199 range in + * http://www.unicode.org/iso15924/iso15924-num.html, of which Arabic and + * Hebrew are by far the most widely used. We also recognize Thaana, N'Ko, and + * Tifinagh, which also have significant modern usage. The rest (Syriac, + * Samaritan, Mandaic, etc.) seem to have extremely limited or no modern usage + * and are not recognized to save on code size. + * The languages usually written in a right-to-left script are taken as those + * with Suppress-Script: Hebr|Arab|Thaa|Nkoo|Tfng in + * http://www.iana.org/assignments/language-subtag-registry, + * as well as Central (or Sorani) Kurdish (ckb), Sindhi (sd) and Uyghur (ug). + * Other subtags of the language code, e.g. regions like EG (Egypt), are + * ignored. + * @param {string} lang BCP 47 (a.k.a III) language code. + * @return {boolean} Whether the language code is an RTL language. + */ +goog.i18n.bidi.isRtlLanguage = function(lang) { + return goog.i18n.bidi.rtlLocalesRe_.test(lang); +}; + + +/** + * Regular expression for bracket guard replacement in html. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.bracketGuardHtmlRe_ = + /(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?(>)+)/g; + + +/** + * Regular expression for bracket guard replacement in text. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.bracketGuardTextRe_ = + /(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)/g; + + +/** + * Apply bracket guard using html span tag. This is to address the problem of + * messy bracket display frequently happens in RTL layout. + * @param {string} s The string that need to be processed. + * @param {boolean=} opt_isRtlContext specifies default direction (usually + * direction of the UI). + * @return {string} The processed string, with all bracket guarded. + */ +goog.i18n.bidi.guardBracketInHtml = function(s, opt_isRtlContext) { + var useRtl = opt_isRtlContext === undefined ? + goog.i18n.bidi.hasAnyRtl(s) : opt_isRtlContext; + if (useRtl) { + return s.replace(goog.i18n.bidi.bracketGuardHtmlRe_, + '<span dir=rtl>$&</span>'); + } + return s.replace(goog.i18n.bidi.bracketGuardHtmlRe_, + '<span dir=ltr>$&</span>'); +}; + + +/** + * Apply bracket guard using LRM and RLM. This is to address the problem of + * messy bracket display frequently happens in RTL layout. + * This version works for both plain text and html. But it does not work as + * good as guardBracketInHtml in some cases. + * @param {string} s The string that need to be processed. + * @param {boolean=} opt_isRtlContext specifies default direction (usually + * direction of the UI). + * @return {string} The processed string, with all bracket guarded. + */ +goog.i18n.bidi.guardBracketInText = function(s, opt_isRtlContext) { + var useRtl = opt_isRtlContext === undefined ? + goog.i18n.bidi.hasAnyRtl(s) : opt_isRtlContext; + var mark = useRtl ? goog.i18n.bidi.Format.RLM : goog.i18n.bidi.Format.LRM; + return s.replace(goog.i18n.bidi.bracketGuardTextRe_, mark + '$&' + mark); +}; + + +/** + * Enforce the html snippet in RTL directionality regardless overall context. + * If the html piece was enclosed by tag, dir will be applied to existing + * tag, otherwise a span tag will be added as wrapper. For this reason, if + * html snippet start with with tag, this tag must enclose the whole piece. If + * the tag already has a dir specified, this new one will override existing + * one in behavior (tested on FF and IE). + * @param {string} html The string that need to be processed. + * @return {string} The processed string, with directionality enforced to RTL. + */ +goog.i18n.bidi.enforceRtlInHtml = function(html) { + if (html.charAt(0) == '<') { + return html.replace(/<\w+/, '$& dir=rtl'); + } + // '\n' is important for FF so that it won't incorrectly merge span groups + return '\n<span dir=rtl>' + html + '</span>'; +}; + + +/** + * Enforce RTL on both end of the given text piece using unicode BiDi formatting + * characters RLE and PDF. + * @param {string} text The piece of text that need to be wrapped. + * @return {string} The wrapped string after process. + */ +goog.i18n.bidi.enforceRtlInText = function(text) { + return goog.i18n.bidi.Format.RLE + text + goog.i18n.bidi.Format.PDF; +}; + + +/** + * Enforce the html snippet in RTL directionality regardless overall context. + * If the html piece was enclosed by tag, dir will be applied to existing + * tag, otherwise a span tag will be added as wrapper. For this reason, if + * html snippet start with with tag, this tag must enclose the whole piece. If + * the tag already has a dir specified, this new one will override existing + * one in behavior (tested on FF and IE). + * @param {string} html The string that need to be processed. + * @return {string} The processed string, with directionality enforced to RTL. + */ +goog.i18n.bidi.enforceLtrInHtml = function(html) { + if (html.charAt(0) == '<') { + return html.replace(/<\w+/, '$& dir=ltr'); + } + // '\n' is important for FF so that it won't incorrectly merge span groups + return '\n<span dir=ltr>' + html + '</span>'; +}; + + +/** + * Enforce LTR on both end of the given text piece using unicode BiDi formatting + * characters LRE and PDF. + * @param {string} text The piece of text that need to be wrapped. + * @return {string} The wrapped string after process. + */ +goog.i18n.bidi.enforceLtrInText = function(text) { + return goog.i18n.bidi.Format.LRE + text + goog.i18n.bidi.Format.PDF; +}; + + +/** + * Regular expression to find dimensions such as "padding: .3 0.4ex 5px 6;" + * @type {RegExp} + * @private + */ +goog.i18n.bidi.dimensionsRe_ = + /:\s*([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)/g; + + +/** + * Regular expression for left. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.leftRe_ = /left/gi; + + +/** + * Regular expression for right. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.rightRe_ = /right/gi; + + +/** + * Placeholder regular expression for swapping. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.tempRe_ = /%%%%/g; + + +/** + * Swap location parameters and 'left'/'right' in CSS specification. The + * processed string will be suited for RTL layout. Though this function can + * cover most cases, there are always exceptions. It is suggested to put + * those exceptions in separate group of CSS string. + * @param {string} cssStr CSS spefication string. + * @return {string} Processed CSS specification string. + */ +goog.i18n.bidi.mirrorCSS = function(cssStr) { + return cssStr. + // reverse dimensions + replace(goog.i18n.bidi.dimensionsRe_, ':$1 $4 $3 $2'). + replace(goog.i18n.bidi.leftRe_, '%%%%'). // swap left and right + replace(goog.i18n.bidi.rightRe_, goog.i18n.bidi.LEFT). + replace(goog.i18n.bidi.tempRe_, goog.i18n.bidi.RIGHT); +}; + + +/** + * Regular expression for hebrew double quote substitution, finding quote + * directly after hebrew characters. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.doubleQuoteSubstituteRe_ = /([\u0591-\u05f2])"/g; + + +/** + * Regular expression for hebrew single quote substitution, finding quote + * directly after hebrew characters. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.singleQuoteSubstituteRe_ = /([\u0591-\u05f2])'/g; + + +/** + * Replace the double and single quote directly after a Hebrew character with + * GERESH and GERSHAYIM. In such case, most likely that's user intention. + * @param {string} str String that need to be processed. + * @return {string} Processed string with double/single quote replaced. + */ +goog.i18n.bidi.normalizeHebrewQuote = function(str) { + return str. + replace(goog.i18n.bidi.doubleQuoteSubstituteRe_, '$1\u05f4'). + replace(goog.i18n.bidi.singleQuoteSubstituteRe_, '$1\u05f3'); +}; + + +/** + * Regular expression to split a string into "words" for directionality + * estimation based on relative word counts. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.wordSeparatorRe_ = /\s+/; + + +/** + * Regular expression to check if a string contains any numerals. Used to + * differentiate between completely neutral strings and those containing + * numbers, which are weakly LTR. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.hasNumeralsRe_ = /\d/; + + +/** + * This constant controls threshold of RTL directionality. + * @type {number} + * @private + */ +goog.i18n.bidi.rtlDetectionThreshold_ = 0.40; + + +/** + * Estimates the directionality of a string based on relative word counts. + * If the number of RTL words is above a certain percentage of the total number + * of strongly directional words, returns RTL. + * Otherwise, if any words are strongly or weakly LTR, returns LTR. + * Otherwise, returns UNKNOWN, which is used to mean "neutral". + * Numbers are counted as weakly LTR. + * @param {string} str The string to be checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {goog.i18n.bidi.Dir} Estimated overall directionality of {@code str}. + */ +goog.i18n.bidi.estimateDirection = function(str, opt_isHtml) { + var rtlCount = 0; + var totalCount = 0; + var hasWeaklyLtr = false; + var tokens = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml). + split(goog.i18n.bidi.wordSeparatorRe_); + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i]; + if (goog.i18n.bidi.startsWithRtl(token)) { + rtlCount++; + totalCount++; + } else if (goog.i18n.bidi.isRequiredLtrRe_.test(token)) { + hasWeaklyLtr = true; + } else if (goog.i18n.bidi.hasAnyLtr(token)) { + totalCount++; + } else if (goog.i18n.bidi.hasNumeralsRe_.test(token)) { + hasWeaklyLtr = true; + } + } + + return totalCount == 0 ? + (hasWeaklyLtr ? goog.i18n.bidi.Dir.LTR : goog.i18n.bidi.Dir.NEUTRAL) : + (rtlCount / totalCount > goog.i18n.bidi.rtlDetectionThreshold_ ? + goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR); +}; + + +/** + * Check the directionality of a piece of text, return true if the piece of + * text should be laid out in RTL direction. + * @param {string} str The piece of text that need to be detected. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether this piece of text should be laid out in RTL. + */ +goog.i18n.bidi.detectRtlDirectionality = function(str, opt_isHtml) { + return goog.i18n.bidi.estimateDirection(str, opt_isHtml) == + goog.i18n.bidi.Dir.RTL; +}; + + +/** + * Sets text input element's directionality and text alignment based on a + * given directionality. Does nothing if the given directionality is unknown or + * neutral. + * @param {Element} element Input field element to set directionality to. + * @param {goog.i18n.bidi.Dir|number|boolean|null} dir Desired directionality, + * given in one of the following formats: + * 1. A goog.i18n.bidi.Dir constant. + * 2. A number (positive = LRT, negative = RTL, 0 = neutral). + * 3. A boolean (true = RTL, false = LTR). + * 4. A null for unknown directionality. + */ +goog.i18n.bidi.setElementDirAndAlign = function(element, dir) { + if (element) { + dir = goog.i18n.bidi.toDir(dir); + if (dir) { + element.style.textAlign = + dir == goog.i18n.bidi.Dir.RTL ? + goog.i18n.bidi.RIGHT : goog.i18n.bidi.LEFT; + element.dir = dir == goog.i18n.bidi.Dir.RTL ? 'rtl' : 'ltr'; + } + } +}; + + + +/** + * Strings that have an (optional) known direction. + * + * Implementations of this interface are string-like objects that carry an + * attached direction, if known. + * @interface + */ +goog.i18n.bidi.DirectionalString = function() {}; + + +/** + * Interface marker of the DirectionalString interface. + * + * This property can be used to determine at runtime whether or not an object + * implements this interface. All implementations of this interface set this + * property to {@code true}. + * @type {boolean} + */ +goog.i18n.bidi.DirectionalString.prototype. + implementsGoogI18nBidiDirectionalString; + + +/** + * Retrieves this object's known direction (if any). + * @return {?goog.i18n.bidi.Dir} The known direction. Null if unknown. + */ +goog.i18n.bidi.DirectionalString.prototype.getDirection; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/iter/iter.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/iter/iter.js new file mode 100644 index 0000000..b7744d5 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/iter/iter.js @@ -0,0 +1,1314 @@ +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Python style iteration utilities. + * @author arv@google.com (Erik Arvidsson) + */ + + +goog.provide('goog.iter'); +goog.provide('goog.iter.Iterable'); +goog.provide('goog.iter.Iterator'); +goog.provide('goog.iter.StopIteration'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.functions'); +goog.require('goog.math'); + + +/** + * @typedef {goog.iter.Iterator|{length:number}|{__iterator__}} + */ +goog.iter.Iterable; + + +// For script engines that already support iterators. +if ('StopIteration' in goog.global) { + /** + * Singleton Error object that is used to terminate iterations. + * @type {Error} + */ + goog.iter.StopIteration = goog.global['StopIteration']; +} else { + /** + * Singleton Error object that is used to terminate iterations. + * @type {Error} + * @suppress {duplicate} + */ + goog.iter.StopIteration = Error('StopIteration'); +} + + + +/** + * Class/interface for iterators. An iterator needs to implement a {@code next} + * method and it needs to throw a {@code goog.iter.StopIteration} when the + * iteration passes beyond the end. Iterators have no {@code hasNext} method. + * It is recommended to always use the helper functions to iterate over the + * iterator or in case you are only targeting JavaScript 1.7 for in loops. + * @constructor + * @template VALUE + */ +goog.iter.Iterator = function() {}; + + +/** + * Returns the next value of the iteration. This will throw the object + * {@see goog.iter#StopIteration} when the iteration passes the end. + * @return {VALUE} Any object or value. + */ +goog.iter.Iterator.prototype.next = function() { + throw goog.iter.StopIteration; +}; + + +/** + * Returns the {@code Iterator} object itself. This is used to implement + * the iterator protocol in JavaScript 1.7 + * @param {boolean=} opt_keys Whether to return the keys or values. Default is + * to only return the values. This is being used by the for-in loop (true) + * and the for-each-in loop (false). Even though the param gives a hint + * about what the iterator will return there is no guarantee that it will + * return the keys when true is passed. + * @return {!goog.iter.Iterator.<VALUE>} The object itself. + */ +goog.iter.Iterator.prototype.__iterator__ = function(opt_keys) { + return this; +}; + + +/** + * Returns an iterator that knows how to iterate over the values in the object. + * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable If the + * object is an iterator it will be returned as is. If the object has an + * {@code __iterator__} method that will be called to get the value + * iterator. If the object is an array-like object we create an iterator + * for that. + * @return {!goog.iter.Iterator.<VALUE>} An iterator that knows how to iterate + * over the values in {@code iterable}. + * @template VALUE + */ +goog.iter.toIterator = function(iterable) { + if (iterable instanceof goog.iter.Iterator) { + return iterable; + } + if (typeof iterable.__iterator__ == 'function') { + return iterable.__iterator__(false); + } + if (goog.isArrayLike(iterable)) { + var i = 0; + var newIter = new goog.iter.Iterator; + newIter.next = function() { + while (true) { + if (i >= iterable.length) { + throw goog.iter.StopIteration; + } + // Don't include deleted elements. + if (!(i in iterable)) { + i++; + continue; + } + return iterable[i++]; + } + }; + return newIter; + } + + + // TODO(arv): Should we fall back on goog.structs.getValues()? + throw Error('Not implemented'); +}; + + +/** + * Calls a function for each element in the iterator with the element of the + * iterator passed as argument. + * + * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator + * to iterate over. If the iterable is an object {@code toIterator} will be + * called on it. + * @param {function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>)| + * function(this:THIS,number,undefined,goog.iter.Iterator.<VALUE>)} f + * The function to call for every element. This function takes 3 arguments + * (the element, undefined, and the iterator) and the return value is + * irrelevant. The reason for passing undefined as the second argument is + * so that the same function can be used in {@see goog.array#forEach} as + * well as others. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @template THIS, VALUE + */ +goog.iter.forEach = function(iterable, f, opt_obj) { + if (goog.isArrayLike(iterable)) { + /** @preserveTry */ + try { + // NOTES: this passes the index number to the second parameter + // of the callback contrary to the documentation above. + goog.array.forEach(/** @type {goog.array.ArrayLike} */(iterable), f, + opt_obj); + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } + } + } else { + iterable = goog.iter.toIterator(iterable); + /** @preserveTry */ + try { + while (true) { + f.call(opt_obj, iterable.next(), undefined, iterable); + } + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } + } + } +}; + + +/** + * Calls a function for every element in the iterator, and if the function + * returns true adds the element to a new iterator. + * + * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator + * to iterate over. + * @param { + * function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>):boolean} f + * The function to call for every element. This function takes 3 arguments + * (the element, undefined, and the iterator) and should return a boolean. + * If the return value is true the element will be included in the returned + * iterator. If it is false the element is not included. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {!goog.iter.Iterator.<VALUE>} A new iterator in which only elements + * that passed the test are present. + * @template THIS, VALUE + */ +goog.iter.filter = function(iterable, f, opt_obj) { + var iterator = goog.iter.toIterator(iterable); + var newIter = new goog.iter.Iterator; + newIter.next = function() { + while (true) { + var val = iterator.next(); + if (f.call(opt_obj, val, undefined, iterator)) { + return val; + } + } + }; + return newIter; +}; + + +/** + * Calls a function for every element in the iterator, and if the function + * returns false adds the element to a new iterator. + * + * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator + * to iterate over. + * @param { + * function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>):boolean} f + * The function to call for every element. This function takes 3 arguments + * (the element, undefined, and the iterator) and should return a boolean. + * If the return value is false the element will be included in the returned + * iterator. If it is true the element is not included. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {!goog.iter.Iterator.<VALUE>} A new iterator in which only elements + * that did not pass the test are present. + * @template THIS, VALUE + */ +goog.iter.filterFalse = function(iterable, f, opt_obj) { + return goog.iter.filter(iterable, goog.functions.not(f), opt_obj); +}; + + +/** + * Creates a new iterator that returns the values in a range. This function + * can take 1, 2 or 3 arguments: + * <pre> + * range(5) same as range(0, 5, 1) + * range(2, 5) same as range(2, 5, 1) + * </pre> + * + * @param {number} startOrStop The stop value if only one argument is provided. + * The start value if 2 or more arguments are provided. If only one + * argument is used the start value is 0. + * @param {number=} opt_stop The stop value. If left out then the first + * argument is used as the stop value. + * @param {number=} opt_step The number to increment with between each call to + * next. This can be negative. + * @return {!goog.iter.Iterator.<number>} A new iterator that returns the values + * in the range. + */ +goog.iter.range = function(startOrStop, opt_stop, opt_step) { + var start = 0; + var stop = startOrStop; + var step = opt_step || 1; + if (arguments.length > 1) { + start = startOrStop; + stop = opt_stop; + } + if (step == 0) { + throw Error('Range step argument must not be zero'); + } + + var newIter = new goog.iter.Iterator; + newIter.next = function() { + if (step > 0 && start >= stop || step < 0 && start <= stop) { + throw goog.iter.StopIteration; + } + var rv = start; + start += step; + return rv; + }; + return newIter; +}; + + +/** + * Joins the values in a iterator with a delimiter. + * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator + * to get the values from. + * @param {string} deliminator The text to put between the values. + * @return {string} The joined value string. + * @template VALUE + */ +goog.iter.join = function(iterable, deliminator) { + return goog.iter.toArray(iterable).join(deliminator); +}; + + +/** + * For every element in the iterator call a function and return a new iterator + * with that value. + * + * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The + * iterator to iterate over. + * @param { + * function(this:THIS,VALUE,undefined,!goog.iter.Iterator.<VALUE>):RESULT} f + * The function to call for every element. This function takes 3 arguments + * (the element, undefined, and the iterator) and should return a new value. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {!goog.iter.Iterator.<RESULT>} A new iterator that returns the + * results of applying the function to each element in the original + * iterator. + * @template THIS, VALUE, RESULT + */ +goog.iter.map = function(iterable, f, opt_obj) { + var iterator = goog.iter.toIterator(iterable); + var newIter = new goog.iter.Iterator; + newIter.next = function() { + var val = iterator.next(); + return f.call(opt_obj, val, undefined, iterator); + }; + return newIter; +}; + + +/** + * Passes every element of an iterator into a function and accumulates the + * result. + * + * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator + * to iterate over. + * @param {function(this:THIS,VALUE,VALUE):VALUE} f The function to call for + * every element. This function takes 2 arguments (the function's previous + * result or the initial value, and the value of the current element). + * function(previousValue, currentElement) : newValue. + * @param {VALUE} val The initial value to pass into the function on the first + * call. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * f. + * @return {VALUE} Result of evaluating f repeatedly across the values of + * the iterator. + * @template THIS, VALUE + */ +goog.iter.reduce = function(iterable, f, val, opt_obj) { + var rval = val; + goog.iter.forEach(iterable, function(val) { + rval = f.call(opt_obj, rval, val); + }); + return rval; +}; + + +/** + * Goes through the values in the iterator. Calls f for each of these, and if + * any of them returns true, this returns true (without checking the rest). If + * all return false this will return false. + * + * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator + * object. + * @param { + * function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>):boolean} f + * The function to call for every value. This function takes 3 arguments + * (the value, undefined, and the iterator) and should return a boolean. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {boolean} true if any value passes the test. + * @template THIS, VALUE + */ +goog.iter.some = function(iterable, f, opt_obj) { + iterable = goog.iter.toIterator(iterable); + /** @preserveTry */ + try { + while (true) { + if (f.call(opt_obj, iterable.next(), undefined, iterable)) { + return true; + } + } + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } + } + return false; +}; + + +/** + * Goes through the values in the iterator. Calls f for each of these and if any + * of them returns false this returns false (without checking the rest). If all + * return true this will return true. + * + * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator + * object. + * @param { + * function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>):boolean} f + * The function to call for every value. This function takes 3 arguments + * (the value, undefined, and the iterator) and should return a boolean. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {boolean} true if every value passes the test. + * @template THIS, VALUE + */ +goog.iter.every = function(iterable, f, opt_obj) { + iterable = goog.iter.toIterator(iterable); + /** @preserveTry */ + try { + while (true) { + if (!f.call(opt_obj, iterable.next(), undefined, iterable)) { + return false; + } + } + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } + } + return true; +}; + + +/** + * Takes zero or more iterables and returns one iterator that will iterate over + * them in the order chained. + * @param {...!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} var_args Any + * number of iterable objects. + * @return {!goog.iter.Iterator.<VALUE>} Returns a new iterator that will + * iterate over all the given iterables' contents. + * @template VALUE + */ +goog.iter.chain = function(var_args) { + var iterator = goog.iter.toIterator(arguments); + var iter = new goog.iter.Iterator(); + var current = null; + + iter.next = function() { + while (true) { + if (current == null) { + var it = iterator.next(); + current = goog.iter.toIterator(it); + } + try { + return current.next(); + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } + current = null; + } + } + }; + + return iter; +}; + + +/** + * Takes a single iterable containing zero or more iterables and returns one + * iterator that will iterate over each one in the order given. + * @see http://docs.python.org/2/library/itertools.html#itertools.chain.from_iterable + * @param {goog.iter.Iterable} iterable The iterable of iterables to chain. + * @return {!goog.iter.Iterator.<VALUE>} Returns a new iterator that will + * iterate over all the contents of the iterables contained within + * {@code iterable}. + * @template VALUE + */ +goog.iter.chainFromIterable = function(iterable) { + return goog.iter.chain.apply(undefined, iterable); +}; + + +/** + * Builds a new iterator that iterates over the original, but skips elements as + * long as a supplied function returns true. + * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator + * object. + * @param { + * function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>):boolean} f + * The function to call for every value. This function takes 3 arguments + * (the value, undefined, and the iterator) and should return a boolean. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {!goog.iter.Iterator.<VALUE>} A new iterator that drops elements from + * the original iterator as long as {@code f} is true. + * @template THIS, VALUE + */ +goog.iter.dropWhile = function(iterable, f, opt_obj) { + var iterator = goog.iter.toIterator(iterable); + var newIter = new goog.iter.Iterator; + var dropping = true; + newIter.next = function() { + while (true) { + var val = iterator.next(); + if (dropping && f.call(opt_obj, val, undefined, iterator)) { + continue; + } else { + dropping = false; + } + return val; + } + }; + return newIter; +}; + + +/** + * Builds a new iterator that iterates over the original, but only as long as a + * supplied function returns true. + * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator + * object. + * @param { + * function(this:THIS,VALUE,undefined,goog.iter.Iterator.<VALUE>):boolean} f + * The function to call for every value. This function takes 3 arguments + * (the value, undefined, and the iterator) and should return a boolean. + * @param {THIS=} opt_obj This is used as the 'this' object in f when called. + * @return {!goog.iter.Iterator.<VALUE>} A new iterator that keeps elements in + * the original iterator as long as the function is true. + * @template THIS, VALUE + */ +goog.iter.takeWhile = function(iterable, f, opt_obj) { + var iterator = goog.iter.toIterator(iterable); + var newIter = new goog.iter.Iterator; + var taking = true; + newIter.next = function() { + while (true) { + if (taking) { + var val = iterator.next(); + if (f.call(opt_obj, val, undefined, iterator)) { + return val; + } else { + taking = false; + } + } else { + throw goog.iter.StopIteration; + } + } + }; + return newIter; +}; + + +/** + * Converts the iterator to an array + * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterator + * to convert to an array. + * @return {!Array.<VALUE>} An array of the elements the iterator iterates over. + * @template VALUE + */ +goog.iter.toArray = function(iterable) { + // Fast path for array-like. + if (goog.isArrayLike(iterable)) { + return goog.array.toArray(/** @type {!goog.array.ArrayLike} */(iterable)); + } + iterable = goog.iter.toIterator(iterable); + var array = []; + goog.iter.forEach(iterable, function(val) { + array.push(val); + }); + return array; +}; + + +/** + * Iterates over two iterables and returns true if they contain the same + * sequence of elements and have the same length. + * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable1 The first + * iterable object. + * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable2 The second + * iterable object. + * @return {boolean} true if the iterables contain the same sequence of elements + * and have the same length. + * @template VALUE + */ +goog.iter.equals = function(iterable1, iterable2) { + var fillValue = {}; + var pairs = goog.iter.zipLongest(fillValue, iterable1, iterable2); + return goog.iter.every(pairs, function(pair) { + return pair[0] == pair[1]; + }); +}; + + +/** + * Advances the iterator to the next position, returning the given default value + * instead of throwing an exception if the iterator has no more entries. + * @param {goog.iter.Iterator.<VALUE>|goog.iter.Iterable} iterable The iterable + * object. + * @param {VALUE} defaultValue The value to return if the iterator is empty. + * @return {VALUE} The next item in the iteration, or defaultValue if the + * iterator was empty. + * @template VALUE + */ +goog.iter.nextOrValue = function(iterable, defaultValue) { + try { + return goog.iter.toIterator(iterable).next(); + } catch (e) { + if (e != goog.iter.StopIteration) { + throw e; + } + return defaultValue; + } +}; + + +/** + * Cartesian product of zero or more sets. Gives an iterator that gives every + * combination of one element chosen from each set. For example, + * ([1, 2], [3, 4]) gives ([1, 3], [1, 4], [2, 3], [2, 4]). + * @see http://docs.python.org/library/itertools.html#itertools.product + * @param {...!goog.array.ArrayLike.<VALUE>} var_args Zero or more sets, as + * arrays. + * @return {!goog.iter.Iterator.<!Array.<VALUE>>} An iterator that gives each + * n-tuple (as an array). + * @template VALUE + */ +goog.iter.product = function(var_args) { + var someArrayEmpty = goog.array.some(arguments, function(arr) { + return !arr.length; + }); + + // An empty set in a cartesian product gives an empty set. + if (someArrayEmpty || !arguments.length) { + return new goog.iter.Iterator(); + } + + var iter = new goog.iter.Iterator(); + var arrays = arguments; + + // The first indices are [0, 0, ...] + var indicies = goog.array.repeat(0, arrays.length); + + iter.next = function() { + + if (indicies) { + var retVal = goog.array.map(indicies, function(valueIndex, arrayIndex) { + return arrays[arrayIndex][valueIndex]; + }); + + // Generate the next-largest indices for the next call. + // Increase the rightmost index. If it goes over, increase the next + // rightmost (like carry-over addition). + for (var i = indicies.length - 1; i >= 0; i--) { + // Assertion prevents compiler warning below. + goog.asserts.assert(indicies); + if (indicies[i] < arrays[i].length - 1) { + indicies[i]++; + break; + } + + // We're at the last indices (the last element of every array), so + // the iteration is over on the next call. + if (i == 0) { + indicies = null; + break; + } + // Reset the index in this column and loop back to increment the + // next one. + indicies[i] = 0; + } + return retVal; + } + + throw goog.iter.StopIteration; + }; + + return iter; +}; + + +/** + * Create an iterator to cycle over the iterable's elements indefinitely. + * For example, ([1, 2, 3]) would return : 1, 2, 3, 1, 2, 3, ... + * @see: http://docs.python.org/library/itertools.html#itertools.cycle. + * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The + * iterable object. + * @return {!goog.iter.Iterator.<VALUE>} An iterator that iterates indefinitely + * over the values in {@code iterable}. + * @template VALUE + */ +goog.iter.cycle = function(iterable) { + var baseIterator = goog.iter.toIterator(iterable); + + // We maintain a cache to store the iterable elements as we iterate + // over them. The cache is used to return elements once we have + // iterated over the iterable once. + var cache = []; + var cacheIndex = 0; + + var iter = new goog.iter.Iterator(); + + // This flag is set after the iterable is iterated over once + var useCache = false; + + iter.next = function() { + var returnElement = null; + + // Pull elements off the original iterator if not using cache + if (!useCache) { + try { + // Return the element from the iterable + returnElement = baseIterator.next(); + cache.push(returnElement); + return returnElement; + } catch (e) { + // If an exception other than StopIteration is thrown + // or if there are no elements to iterate over (the iterable was empty) + // throw an exception + if (e != goog.iter.StopIteration || goog.array.isEmpty(cache)) { + throw e; + } + // set useCache to true after we know that a 'StopIteration' exception + // was thrown and the cache is not empty (to handle the 'empty iterable' + // use case) + useCache = true; + } + } + + returnElement = cache[cacheIndex]; + cacheIndex = (cacheIndex + 1) % cache.length; + + return returnElement; + }; + + return iter; +}; + + +/** + * Creates an iterator that counts indefinitely from a starting value. + * @see http://docs.python.org/2/library/itertools.html#itertools.count + * @param {number=} opt_start The starting value. Default is 0. + * @param {number=} opt_step The number to increment with between each call to + * next. Negative and floating point numbers are allowed. Default is 1. + * @return {!goog.iter.Iterator.<number>} A new iterator that returns the values + * in the series. + */ +goog.iter.count = function(opt_start, opt_step) { + var counter = opt_start || 0; + var step = goog.isDef(opt_step) ? opt_step : 1; + var iter = new goog.iter.Iterator(); + + iter.next = function() { + var returnValue = counter; + counter += step; + return returnValue; + }; + + return iter; +}; + + +/** + * Creates an iterator that returns the same object or value repeatedly. + * @param {VALUE} value Any object or value to repeat. + * @return {!goog.iter.Iterator.<VALUE>} A new iterator that returns the + * repeated value. + * @template VALUE + */ +goog.iter.repeat = function(value) { + var iter = new goog.iter.Iterator(); + + iter.next = goog.functions.constant(value); + + return iter; +}; + + +/** + * Creates an iterator that returns running totals from the numbers in + * {@code iterable}. For example, the array {@code [1, 2, 3, 4, 5]} yields + * {@code 1 -> 3 -> 6 -> 10 -> 15}. + * @see http://docs.python.org/3.2/library/itertools.html#itertools.accumulate + * @param {!goog.iter.Iterable.<number>} iterable The iterable of numbers to + * accumulate. + * @return {!goog.iter.Iterator.<number>} A new iterator that returns the + * numbers in the series. + */ +goog.iter.accumulate = function(iterable) { + var iterator = goog.iter.toIterator(iterable); + var total = 0; + var iter = new goog.iter.Iterator(); + + iter.next = function() { + total += iterator.next(); + return total; + }; + + return iter; +}; + + +/** + * Creates an iterator that returns arrays containing the ith elements from the + * provided iterables. The returned arrays will be the same size as the number + * of iterables given in {@code var_args}. Once the shortest iterable is + * exhausted, subsequent calls to {@code next()} will throw + * {@code goog.iter.StopIteration}. + * @see http://docs.python.org/2/library/itertools.html#itertools.izip + * @param {...!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} var_args Any + * number of iterable objects. + * @return {!goog.iter.Iterator.<!Array.<VALUE>>} A new iterator that returns + * arrays of elements from the provided iterables. + * @template VALUE + */ +goog.iter.zip = function(var_args) { + var args = arguments; + var iter = new goog.iter.Iterator(); + + if (args.length > 0) { + var iterators = goog.array.map(args, goog.iter.toIterator); + iter.next = function() { + var arr = goog.array.map(iterators, function(it) { + return it.next(); + }); + return arr; + }; + } + + return iter; +}; + + +/** + * Creates an iterator that returns arrays containing the ith elements from the + * provided iterables. The returned arrays will be the same size as the number + * of iterables given in {@code var_args}. Shorter iterables will be extended + * with {@code fillValue}. Once the longest iterable is exhausted, subsequent + * calls to {@code next()} will throw {@code goog.iter.StopIteration}. + * @see http://docs.python.org/2/library/itertools.html#itertools.izip_longest + * @param {VALUE} fillValue The object or value used to fill shorter iterables. + * @param {...!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} var_args Any + * number of iterable objects. + * @return {!goog.iter.Iterator.<!Array.<VALUE>>} A new iterator that returns + * arrays of elements from the provided iterables. + * @template VALUE + */ +goog.iter.zipLongest = function(fillValue, var_args) { + var args = goog.array.slice(arguments, 1); + var iter = new goog.iter.Iterator(); + + if (args.length > 0) { + var iterators = goog.array.map(args, goog.iter.toIterator); + + iter.next = function() { + var iteratorsHaveValues = false; // false when all iterators are empty. + var arr = goog.array.map(iterators, function(it) { + var returnValue; + try { + returnValue = it.next(); + // Iterator had a value, so we've not exhausted the iterators. + // Set flag accordingly. + iteratorsHaveValues = true; + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } + returnValue = fillValue; + } + return returnValue; + }); + + if (!iteratorsHaveValues) { + throw goog.iter.StopIteration; + } + return arr; + }; + } + + return iter; +}; + + +/** + * Creates an iterator that filters {@code iterable} based on a series of + * {@code selectors}. On each call to {@code next()}, one item is taken from + * both the {@code iterable} and {@code selectors} iterators. If the item from + * {@code selectors} evaluates to true, the item from {@code iterable} is given. + * Otherwise, it is skipped. Once either {@code iterable} or {@code selectors} + * is exhausted, subsequent calls to {@code next()} will throw + * {@code goog.iter.StopIteration}. + * @see http://docs.python.org/2/library/itertools.html#itertools.compress + * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The + * iterable to filter. + * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} selectors An + * iterable of items to be evaluated in a boolean context to determine if + * the corresponding element in {@code iterable} should be included in the + * result. + * @return {!goog.iter.Iterator.<VALUE>} A new iterator that returns the + * filtered values. + * @template VALUE + */ +goog.iter.compress = function(iterable, selectors) { + var selectorIterator = goog.iter.toIterator(selectors); + + return goog.iter.filter(iterable, function() { + return !!selectorIterator.next(); + }); +}; + + + +/** + * Implements the {@code goog.iter.groupBy} iterator. + * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The + * iterable to group. + * @param {function(...[VALUE]): KEY=} opt_keyFunc Optional function for + * determining the key value for each group in the {@code iterable}. Default + * is the identity function. + * @constructor + * @extends {goog.iter.Iterator.<!Array>} + * @template KEY, VALUE + * @private + */ +goog.iter.GroupByIterator_ = function(iterable, opt_keyFunc) { + + /** + * The iterable to group, coerced to an iterator. + * @type {!goog.iter.Iterator} + */ + this.iterator = goog.iter.toIterator(iterable); + + /** + * A function for determining the key value for each element in the iterable. + * If no function is provided, the identity function is used and returns the + * element unchanged. + * @type {function(...[VALUE]): KEY} + */ + this.keyFunc = opt_keyFunc || goog.functions.identity; + + /** + * The target key for determining the start of a group. + * @type {KEY} + */ + this.targetKey; + + /** + * The current key visited during iteration. + * @type {KEY} + */ + this.currentKey; + + /** + * The current value being added to the group. + * @type {VALUE} + */ + this.currentValue; +}; +goog.inherits(goog.iter.GroupByIterator_, goog.iter.Iterator); + + +/** @override */ +goog.iter.GroupByIterator_.prototype.next = function() { + while (this.currentKey == this.targetKey) { + this.currentValue = this.iterator.next(); // Exits on StopIteration + this.currentKey = this.keyFunc(this.currentValue); + } + this.targetKey = this.currentKey; + return [this.currentKey, this.groupItems_(this.targetKey)]; +}; + + +/** + * Performs the grouping of objects using the given key. + * @param {KEY} targetKey The target key object for the group. + * @return {!Array.<VALUE>} An array of grouped objects. + * @private + */ +goog.iter.GroupByIterator_.prototype.groupItems_ = function(targetKey) { + var arr = []; + while (this.currentKey == targetKey) { + arr.push(this.currentValue); + try { + this.currentValue = this.iterator.next(); + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } + break; + } + this.currentKey = this.keyFunc(this.currentValue); + } + return arr; +}; + + +/** + * Creates an iterator that returns arrays containing elements from the + * {@code iterable} grouped by a key value. For iterables with repeated + * elements (i.e. sorted according to a particular key function), this function + * has a {@code uniq}-like effect. For example, grouping the array: + * {@code [A, B, B, C, C, A]} produces + * {@code [A, [A]], [B, [B, B]], [C, [C, C]], [A, [A]]}. + * @see http://docs.python.org/2/library/itertools.html#itertools.groupby + * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The + * iterable to group. + * @param {function(...[VALUE]): KEY=} opt_keyFunc Optional function for + * determining the key value for each group in the {@code iterable}. Default + * is the identity function. + * @return {!goog.iter.Iterator.<!Array>} A new iterator that returns arrays of + * consecutive key and groups. + * @template KEY, VALUE + */ +goog.iter.groupBy = function(iterable, opt_keyFunc) { + return new goog.iter.GroupByIterator_(iterable, opt_keyFunc); +}; + + +/** + * Gives an iterator that gives the result of calling the given function + * <code>f</code> with the arguments taken from the next element from + * <code>iterable</code> (the elements are expected to also be iterables). + * + * Similar to {@see goog.iter#map} but allows the function to accept multiple + * arguments from the iterable. + * + * @param {!goog.iter.Iterable.<!goog.iter.Iterable>} iterable The iterable of + * iterables to iterate over. + * @param {function(this:THIS,...[*]):RESULT} f The function to call for every + * element. This function takes N+2 arguments, where N represents the + * number of items from the next element of the iterable. The two + * additional arguments passed to the function are undefined and the + * iterator itself. The function should return a new value. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {!goog.iter.Iterator.<RESULT>} A new iterator that returns the + * results of applying the function to each element in the original + * iterator. + * @template THIS, RESULT + */ +goog.iter.starMap = function(iterable, f, opt_obj) { + var iterator = goog.iter.toIterator(iterable); + var iter = new goog.iter.Iterator(); + + iter.next = function() { + var args = goog.iter.toArray(iterator.next()); + return f.apply(opt_obj, goog.array.concat(args, undefined, iterator)); + }; + + return iter; +}; + + +/** + * Returns an array of iterators each of which can iterate over the values in + * {@code iterable} without advancing the others. + * @see http://docs.python.org/2/library/itertools.html#itertools.tee + * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The + * iterable to tee. + * @param {number=} opt_num The number of iterators to create. Default is 2. + * @return {!Array.<goog.iter.Iterator.<VALUE>>} An array of iterators. + * @template VALUE + */ +goog.iter.tee = function(iterable, opt_num) { + var iterator = goog.iter.toIterator(iterable); + var num = goog.isNumber(opt_num) ? opt_num : 2; + var buffers = goog.array.map(goog.array.range(num), function() { + return []; + }); + + var addNextIteratorValueToBuffers = function() { + var val = iterator.next(); + goog.array.forEach(buffers, function(buffer) { + buffer.push(val); + }); + }; + + var createIterator = function(buffer) { + // Each tee'd iterator has an associated buffer (initially empty). When a + // tee'd iterator's buffer is empty, it calls + // addNextIteratorValueToBuffers(), adding the next value to all tee'd + // iterators' buffers, and then returns that value. This allows each + // iterator to be advanced independently. + var iter = new goog.iter.Iterator(); + + iter.next = function() { + if (goog.array.isEmpty(buffer)) { + addNextIteratorValueToBuffers(); + } + goog.asserts.assert(!goog.array.isEmpty(buffer)); + return buffer.shift(); + }; + + return iter; + }; + + return goog.array.map(buffers, createIterator); +}; + + +/** + * Creates an iterator that returns arrays containing a count and an element + * obtained from the given {@code iterable}. + * @see http://docs.python.org/2/library/functions.html#enumerate + * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The + * iterable to enumerate. + * @param {number=} opt_start Optional starting value. Default is 0. + * @return {!goog.iter.Iterator.<!Array>} A new iterator containing count/item + * pairs. + * @template VALUE + */ +goog.iter.enumerate = function(iterable, opt_start) { + return goog.iter.zip(goog.iter.count(opt_start), iterable); +}; + + +/** + * Creates an iterator that returns the first {@code limitSize} elements from an + * iterable. If this number is greater than the number of elements in the + * iterable, all the elements are returned. + * @see http://goo.gl/V0sihp Inspired by the limit iterator in Guava. + * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The + * iterable to limit. + * @param {number} limitSize The maximum number of elements to return. + * @return {!goog.iter.Iterator.<VALUE>} A new iterator containing + * {@code limitSize} elements. + * @template VALUE + */ +goog.iter.limit = function(iterable, limitSize) { + goog.asserts.assert(goog.math.isInt(limitSize) && limitSize >= 0); + + var iterator = goog.iter.toIterator(iterable); + + var iter = new goog.iter.Iterator(); + var remaining = limitSize; + + iter.next = function() { + if (remaining-- > 0) { + return iterator.next(); + } + throw goog.iter.StopIteration; + }; + + return iter; +}; + + +/** + * Creates an iterator that is advanced {@code count} steps ahead. Consumed + * values are silently discarded. If {@code count} is greater than the number + * of elements in {@code iterable}, an empty iterator is returned. Subsequent + * calls to {@code next()} will throw {@code goog.iter.StopIteration}. + * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The + * iterable to consume. + * @param {number} count The number of elements to consume from the iterator. + * @return {!goog.iter.Iterator.<VALUE>} An iterator advanced zero or more steps + * ahead. + * @template VALUE + */ +goog.iter.consume = function(iterable, count) { + goog.asserts.assert(goog.math.isInt(count) && count >= 0); + + var iterator = goog.iter.toIterator(iterable); + + while (count-- > 0) { + goog.iter.nextOrValue(iterator, null); + } + + return iterator; +}; + + +/** + * Creates an iterator that returns a range of elements from an iterable. + * Similar to {@see goog.array#slice} but does not support negative indexes. + * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The + * iterable to slice. + * @param {number} start The index of the first element to return. + * @param {number=} opt_end The index after the last element to return. If + * defined, must be greater than or equal to {@code start}. + * @return {!goog.iter.Iterator.<VALUE>} A new iterator containing a slice of + * the original. + * @template VALUE + */ +goog.iter.slice = function(iterable, start, opt_end) { + goog.asserts.assert(goog.math.isInt(start) && start >= 0); + + var iterator = goog.iter.consume(iterable, start); + + if (goog.isNumber(opt_end)) { + goog.asserts.assert( + goog.math.isInt(/** @type {number} */ (opt_end)) && opt_end >= start); + iterator = goog.iter.limit(iterator, opt_end - start /* limitSize */); + } + + return iterator; +}; + + +/** + * Checks an array for duplicate elements. + * @param {Array.<VALUE>|goog.array.ArrayLike} arr The array to check for + * duplicates. + * @return {boolean} True, if the array contains duplicates, false otherwise. + * @private + * @template VALUE + */ +// TODO(user): Consider moving this into goog.array as a public function. +goog.iter.hasDuplicates_ = function(arr) { + var deduped = []; + goog.array.removeDuplicates(arr, deduped); + return arr.length != deduped.length; +}; + + +/** + * Creates an iterator that returns permutations of elements in + * {@code iterable}. + * + * Permutations are obtained by taking the Cartesian product of + * {@code opt_length} iterables and filtering out those with repeated + * elements. For example, the permutations of {@code [1,2,3]} are + * {@code [[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]}. + * @see http://docs.python.org/2/library/itertools.html#itertools.permutations + * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The + * iterable from which to generate permutations. + * @param {number=} opt_length Length of each permutation. If omitted, defaults + * to the length of {@code iterable}. + * @return {!goog.iter.Iterator.<!Array.<VALUE>>} A new iterator containing the + * permutations of {@code iterable}. + * @template VALUE + */ +goog.iter.permutations = function(iterable, opt_length) { + var elements = goog.iter.toArray(iterable); + var length = goog.isNumber(opt_length) ? opt_length : elements.length; + + var sets = goog.array.repeat(elements, length); + var product = goog.iter.product.apply(undefined, sets); + + return goog.iter.filter(product, function(arr) { + return !goog.iter.hasDuplicates_(arr); + }); +}; + + +/** + * Creates an iterator that returns combinations of elements from + * {@code iterable}. + * + * Combinations are obtained by taking the {@see goog.iter#permutations} of + * {@code iterable} and filtering those whose elements appear in the order they + * are encountered in {@code iterable}. For example, the 3-length combinations + * of {@code [0,1,2,3]} are {@code [[0,1,2], [0,1,3], [0,2,3], [1,2,3]]}. + * @see http://docs.python.org/2/library/itertools.html#itertools.combinations + * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The + * iterable from which to generate combinations. + * @param {number} length The length of each combination. + * @return {!goog.iter.Iterator.<!Array.<VALUE>>} A new iterator containing + * combinations from the {@code iterable}. + * @template VALUE + */ +goog.iter.combinations = function(iterable, length) { + var elements = goog.iter.toArray(iterable); + var indexes = goog.iter.range(elements.length); + var indexIterator = goog.iter.permutations(indexes, length); + // sortedIndexIterator will now give arrays of with the given length that + // indicate what indexes into "elements" should be returned on each iteration. + var sortedIndexIterator = goog.iter.filter(indexIterator, function(arr) { + return goog.array.isSorted(arr); + }); + + var iter = new goog.iter.Iterator(); + + function getIndexFromElements(index) { + return elements[index]; + } + + iter.next = function() { + return goog.array.map( + /** @type {!Array.<number>} */ + (sortedIndexIterator.next()), getIndexFromElements); + }; + + return iter; +}; + + +/** + * Creates an iterator that returns combinations of elements from + * {@code iterable}, with repeated elements possible. + * + * Combinations are obtained by taking the Cartesian product of {@code length} + * iterables and filtering those whose elements appear in the order they are + * encountered in {@code iterable}. For example, the 2-length combinations of + * {@code [1,2,3]} are {@code [[1,1], [1,2], [1,3], [2,2], [2,3], [3,3]]}. + * @see http://docs.python.org/2/library/itertools.html#itertools.combinations_with_replacement + * @see http://en.wikipedia.org/wiki/Combination#Number_of_combinations_with_repetition + * @param {!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable} iterable The + * iterable to combine. + * @param {number} length The length of each combination. + * @return {!goog.iter.Iterator.<!Array.<VALUE>>} A new iterator containing + * combinations from the {@code iterable}. + * @template VALUE + */ +goog.iter.combinationsWithReplacement = function(iterable, length) { + var elements = goog.iter.toArray(iterable); + var indexes = goog.array.range(elements.length); + var sets = goog.array.repeat(indexes, length); + var indexIterator = goog.iter.product.apply(undefined, sets); + // sortedIndexIterator will now give arrays of with the given length that + // indicate what indexes into "elements" should be returned on each iteration. + var sortedIndexIterator = goog.iter.filter(indexIterator, function(arr) { + return goog.array.isSorted(arr); + }); + + var iter = new goog.iter.Iterator(); + + function getIndexFromElements(index) { + return elements[index]; + } + + iter.next = function() { + return goog.array.map( + /** @type {!Array.<number>} */ + (sortedIndexIterator.next()), getIndexFromElements); + }; + + return iter; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/browser.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/browser.js new file mode 100644 index 0000000..2a53f12 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/browser.js @@ -0,0 +1,271 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Closure user agent detection (Browser). + * @see <a href="http://www.useragentstring.com/">User agent strings</a> + * For more information on rendering engine, platform, or device see the other + * sub-namespaces in goog.labs.userAgent, goog.labs.userAgent.platform, + * goog.labs.userAgent.device respectively.) + * + */ + +goog.provide('goog.labs.userAgent.browser'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.labs.userAgent.util'); +goog.require('goog.string'); + + +/** + * @return {boolean} Whether the user's browser is Opera. + * @private + */ +goog.labs.userAgent.browser.matchOpera_ = function() { + return goog.labs.userAgent.util.matchUserAgent('Opera') || + goog.labs.userAgent.util.matchUserAgent('OPR'); +}; + + +/** + * @return {boolean} Whether the user's browser is IE. + * @private + */ +goog.labs.userAgent.browser.matchIE_ = function() { + return goog.labs.userAgent.util.matchUserAgent('Trident') || + goog.labs.userAgent.util.matchUserAgent('MSIE'); +}; + + +/** + * @return {boolean} Whether the user's browser is Firefox. + * @private + */ +goog.labs.userAgent.browser.matchFirefox_ = function() { + return goog.labs.userAgent.util.matchUserAgent('Firefox'); +}; + + +/** + * @return {boolean} Whether the user's browser is Safari. + * @private + */ +goog.labs.userAgent.browser.matchSafari_ = function() { + return goog.labs.userAgent.util.matchUserAgent('Safari') && + !goog.labs.userAgent.util.matchUserAgent('Chrome') && + !goog.labs.userAgent.util.matchUserAgent('CriOS') && + !goog.labs.userAgent.util.matchUserAgent('Android'); +}; + + +/** + * @return {boolean} Whether the user's browser is Chrome. + * @private + */ +goog.labs.userAgent.browser.matchChrome_ = function() { + return goog.labs.userAgent.util.matchUserAgent('Chrome') || + goog.labs.userAgent.util.matchUserAgent('CriOS'); +}; + + +/** + * @return {boolean} Whether the user's browser is the Android browser. + * @private + */ +goog.labs.userAgent.browser.matchAndroidBrowser_ = function() { + return goog.labs.userAgent.util.matchUserAgent('Android') && + !goog.labs.userAgent.util.matchUserAgent('Chrome') && + !goog.labs.userAgent.util.matchUserAgent('CriOS'); +}; + + +/** + * @return {boolean} Whether the user's browser is Opera. + */ +goog.labs.userAgent.browser.isOpera = goog.labs.userAgent.browser.matchOpera_; + + +/** + * @return {boolean} Whether the user's browser is IE. + */ +goog.labs.userAgent.browser.isIE = goog.labs.userAgent.browser.matchIE_; + + +/** + * @return {boolean} Whether the user's browser is Firefox. + */ +goog.labs.userAgent.browser.isFirefox = + goog.labs.userAgent.browser.matchFirefox_; + + +/** + * @return {boolean} Whether the user's browser is Safari. + */ +goog.labs.userAgent.browser.isSafari = + goog.labs.userAgent.browser.matchSafari_; + + +/** + * @return {boolean} Whether the user's browser is Chrome. + */ +goog.labs.userAgent.browser.isChrome = + goog.labs.userAgent.browser.matchChrome_; + + +/** + * @return {boolean} Whether the user's browser is the Android browser. + */ +goog.labs.userAgent.browser.isAndroidBrowser = + goog.labs.userAgent.browser.matchAndroidBrowser_; + + +/** + * For more information, see: + * http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html + * @return {boolean} Whether the user's browser is Silk. + */ +goog.labs.userAgent.browser.isSilk = function() { + return goog.labs.userAgent.util.matchUserAgent('Silk'); +}; + + +/** + * @return {string} The browser version or empty string if version cannot be + * determined. Note that for Internet Explorer, this returns the version of + * the browser, not the version of the rendering engine. (IE 8 in + * compatibility mode will return 8.0 rather than 7.0. To determine the + * rendering engine version, look at document.documentMode instead. See + * http://msdn.microsoft.com/en-us/library/cc196988(v=vs.85).aspx for more + * details.) + */ +goog.labs.userAgent.browser.getVersion = function() { + var userAgentString = goog.labs.userAgent.util.getUserAgent(); + // Special case IE since IE's version is inside the parenthesis and + // without the '/'. + if (goog.labs.userAgent.browser.isIE()) { + return goog.labs.userAgent.browser.getIEVersion_(userAgentString); + } + + if (goog.labs.userAgent.browser.isOpera()) { + return goog.labs.userAgent.browser.getOperaVersion_(userAgentString); + } + + var versionTuples = + goog.labs.userAgent.util.extractVersionTuples(userAgentString); + return goog.labs.userAgent.browser.getVersionFromTuples_(versionTuples); +}; + + +/** + * @param {string|number} version The version to check. + * @return {boolean} Whether the browser version is higher or the same as the + * given version. + */ +goog.labs.userAgent.browser.isVersionOrHigher = function(version) { + return goog.string.compareVersions(goog.labs.userAgent.browser.getVersion(), + version) >= 0; +}; + + +/** + * Determines IE version. More information: + * http://msdn.microsoft.com/en-us/library/ie/bg182625(v=vs.85).aspx#uaString + * http://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx + * http://blogs.msdn.com/b/ie/archive/2010/03/23/introducing-ie9-s-user-agent-string.aspx + * http://blogs.msdn.com/b/ie/archive/2009/01/09/the-internet-explorer-8-user-agent-string-updated-edition.aspx + * + * @param {string} userAgent the User-Agent. + * @return {string} + * @private + */ +goog.labs.userAgent.browser.getIEVersion_ = function(userAgent) { + // IE11 may identify itself as MSIE 9.0 or MSIE 10.0 due to an IE 11 upgrade + // bug. Example UA: + // Mozilla/5.0 (MSIE 9.0; Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) + // like Gecko. + // See http://www.whatismybrowser.com/developers/unknown-user-agent-fragments. + var rv = /rv: *([\d\.]*)/.exec(userAgent); + if (rv && rv[1]) { + return rv[1]; + } + + var version = ''; + var msie = /MSIE +([\d\.]+)/.exec(userAgent); + if (msie && msie[1]) { + // IE in compatibility mode usually identifies itself as MSIE 7.0; in this + // case, use the Trident version to determine the version of IE. For more + // details, see the links above. + var tridentVersion = /Trident\/(\d.\d)/.exec(userAgent); + if (msie[1] == '7.0') { + if (tridentVersion && tridentVersion[1]) { + switch (tridentVersion[1]) { + case '4.0': + version = '8.0'; + break; + case '5.0': + version = '9.0'; + break; + case '6.0': + version = '10.0'; + break; + case '7.0': + version = '11.0'; + break; + } + } else { + version = '7.0'; + } + } else { + version = msie[1]; + } + } + return version; +}; + + +/** + * Determines Opera version. More information: + * http://my.opera.com/ODIN/blog/2013/07/15/opera-user-agent-strings-opera-15-and-beyond + * + * @param {string} userAgent The User-Agent. + * @return {string} + * @private + */ +goog.labs.userAgent.browser.getOperaVersion_ = function(userAgent) { + var versionTuples = + goog.labs.userAgent.util.extractVersionTuples(userAgent); + var lastTuple = goog.array.peek(versionTuples); + if (lastTuple[0] == 'OPR' && lastTuple[1]) { + return lastTuple[1]; + } + + return goog.labs.userAgent.browser.getVersionFromTuples_(versionTuples); +}; + + +/** + * Nearly all User-Agents start with Mozilla/N.0. This looks at the second tuple + * for the actual browser version number. + * @param {!Array.<!Array.<string>>} versionTuples + * @return {string} The version or empty string if it cannot be determined. + * @private + */ +goog.labs.userAgent.browser.getVersionFromTuples_ = function(versionTuples) { + // versionTuples[2] (The first X/Y tuple after the parenthesis) contains the + // browser version number. + goog.asserts.assert(versionTuples.length > 2, + 'Couldn\'t extract version tuple from user agent string'); + return versionTuples[2] && versionTuples[2][1] ? versionTuples[2][1] : ''; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/engine.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/engine.js new file mode 100644 index 0000000..ab8346c --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/engine.js @@ -0,0 +1,130 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Closure user agent detection. + * @see http://en.wikipedia.org/wiki/User_agent + * For more information on browser brand, platform, or device see the other + * sub-namespaces in goog.labs.userAgent (browser, platform, and device). + * + */ + +goog.provide('goog.labs.userAgent.engine'); + +goog.require('goog.array'); +goog.require('goog.labs.userAgent.util'); +goog.require('goog.string'); + + +/** + * @return {boolean} Whether the rendering engine is Presto. + */ +goog.labs.userAgent.engine.isPresto = function() { + return goog.labs.userAgent.util.matchUserAgent('Presto'); +}; + + +/** + * @return {boolean} Whether the rendering engine is Trident. + */ +goog.labs.userAgent.engine.isTrident = function() { + // IE only started including the Trident token in IE8. + return goog.labs.userAgent.util.matchUserAgent('Trident') || + goog.labs.userAgent.util.matchUserAgent('MSIE'); +}; + + +/** + * @return {boolean} Whether the rendering engine is WebKit. + */ +goog.labs.userAgent.engine.isWebKit = function() { + return goog.labs.userAgent.util.matchUserAgentIgnoreCase('WebKit'); +}; + + +/** + * @return {boolean} Whether the rendering engine is Gecko. + */ +goog.labs.userAgent.engine.isGecko = function() { + return goog.labs.userAgent.util.matchUserAgent('Gecko') && + !goog.labs.userAgent.engine.isWebKit() && + !goog.labs.userAgent.engine.isTrident(); +}; + + +/** + * @return {string} The rendering engine's version or empty string if version + * can't be determined. + */ +goog.labs.userAgent.engine.getVersion = function() { + var userAgentString = goog.labs.userAgent.util.getUserAgent(); + if (userAgentString) { + var tuples = goog.labs.userAgent.util.extractVersionTuples( + userAgentString); + + var engineTuple = tuples[1]; + if (engineTuple) { + // In Gecko, the version string is either in the browser info or the + // Firefox version. See Gecko user agent string reference: + // http://goo.gl/mULqa + if (engineTuple[0] == 'Gecko') { + return goog.labs.userAgent.engine.getVersionForKey_( + tuples, 'Firefox'); + } + + return engineTuple[1]; + } + + // IE has only one version identifier, and the Trident version is + // specified in the parenthetical. + var browserTuple = tuples[0]; + var info; + if (browserTuple && (info = browserTuple[2])) { + var match = /Trident\/([^\s;]+)/.exec(info); + if (match) { + return match[1]; + } + } + } + return ''; +}; + + +/** + * @param {string|number} version The version to check. + * @return {boolean} Whether the rendering engine version is higher or the same + * as the given version. + */ +goog.labs.userAgent.engine.isVersionOrHigher = function(version) { + return goog.string.compareVersions(goog.labs.userAgent.engine.getVersion(), + version) >= 0; +}; + + +/** + * @param {!Array.<!Array.<string>>} tuples Version tuples. + * @param {string} key The key to look for. + * @return {string} The version string of the given key, if present. + * Otherwise, the empty string. + * @private + */ +goog.labs.userAgent.engine.getVersionForKey_ = function(tuples, key) { + // TODO(nnaze): Move to util if useful elsewhere. + + var pair = goog.array.find(tuples, function(pair) { + return key == pair[0]; + }); + + return pair && pair[1] || ''; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/util.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/util.js new file mode 100644 index 0000000..c3665c3 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/labs/useragent/util.js @@ -0,0 +1,154 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utilities used by goog.labs.userAgent tools. These functions + * should not be used outside of goog.labs.userAgent.*. + * + * @visibility {//closure/goog/bin/sizetests:__pkg__} + * @visibility {//closure/goog/dom:__subpackages__} + * @visibility {//closure/goog/style:__pkg__} + * @visibility {//closure/goog/testing:__pkg__} + * @visibility {//closure/goog/useragent:__subpackages__} + * @visibility {//testing/puppet/modules:__pkg__} * + * + * @author nnaze@google.com (Nathan Naze) + */ + +goog.provide('goog.labs.userAgent.util'); + +goog.require('goog.string'); + + +/** + * Gets the native userAgent string from navigator if it exists. + * If navigator or navigator.userAgent string is missing, returns an empty + * string. + * @return {string} + * @private + */ +goog.labs.userAgent.util.getNativeUserAgentString_ = function() { + var navigator = goog.labs.userAgent.util.getNavigator_(); + if (navigator) { + var userAgent = navigator.userAgent; + if (userAgent) { + return userAgent; + } + } + return ''; +}; + + +/** + * Getter for the native navigator. + * This is a separate function so it can be stubbed out in testing. + * @return {Navigator} + * @private + */ +goog.labs.userAgent.util.getNavigator_ = function() { + return goog.global.navigator; +}; + + +/** + * A possible override for applications which wish to not check + * navigator.userAgent but use a specified value for detection instead. + * @private {string} + */ +goog.labs.userAgent.util.userAgent_ = + goog.labs.userAgent.util.getNativeUserAgentString_(); + + +/** + * Applications may override browser detection on the built in + * navigator.userAgent object by setting this string. Set to null to use the + * browser object instead. + * @param {?string=} opt_userAgent The User-Agent override. + */ +goog.labs.userAgent.util.setUserAgent = function(opt_userAgent) { + goog.labs.userAgent.util.userAgent_ = opt_userAgent || + goog.labs.userAgent.util.getNativeUserAgentString_(); +}; + + +/** + * @return {string} The user agent string. + */ +goog.labs.userAgent.util.getUserAgent = function() { + return goog.labs.userAgent.util.userAgent_; +}; + + +/** + * @param {string} str + * @return {boolean} Whether the user agent contains the given string, ignoring + * case. + */ +goog.labs.userAgent.util.matchUserAgent = function(str) { + var userAgent = goog.labs.userAgent.util.getUserAgent(); + return goog.string.contains(userAgent, str); +}; + + +/** + * @param {string} str + * @return {boolean} Whether the user agent contains the given string. + */ +goog.labs.userAgent.util.matchUserAgentIgnoreCase = function(str) { + var userAgent = goog.labs.userAgent.util.getUserAgent(); + return goog.string.caseInsensitiveContains(userAgent, str); +}; + + +/** + * Parses the user agent into tuples for each section. + * @param {string} userAgent + * @return {!Array.<!Array.<string>>} Tuples of key, version, and the contents + * of the parenthetical. + */ +goog.labs.userAgent.util.extractVersionTuples = function(userAgent) { + // Matches each section of a user agent string. + // Example UA: + // Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us) + // AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405 + // This has three version tuples: Mozilla, AppleWebKit, and Mobile. + + var versionRegExp = new RegExp( + // Key. Note that a key may have a space. + // (i.e. 'Mobile Safari' in 'Mobile Safari/5.0') + '(\\w[\\w ]+)' + + + '/' + // slash + '([^\\s]+)' + // version (i.e. '5.0b') + '\\s*' + // whitespace + '(?:\\((.*?)\\))?', // parenthetical info. parentheses not matched. + 'g'); + + var data = []; + var match; + + // Iterate and collect the version tuples. Each iteration will be the + // next regex match. + while (match = versionRegExp.exec(userAgent)) { + data.push([ + match[1], // key + match[2], // value + // || undefined as this is not undefined in IE7 and IE8 + match[3] || undefined // info + ]); + } + + return data; +}; + diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/log/log.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/log/log.js new file mode 100644 index 0000000..b93d835 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/log/log.js @@ -0,0 +1,197 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Basic strippable logging definitions. + * @see http://go/closurelogging + * + * @author johnlenz@google.com (John Lenz) + */ + +goog.provide('goog.log'); +goog.provide('goog.log.Level'); +goog.provide('goog.log.LogRecord'); +goog.provide('goog.log.Logger'); + +goog.require('goog.debug'); +goog.require('goog.debug.LogManager'); +goog.require('goog.debug.LogRecord'); +goog.require('goog.debug.Logger'); + + +/** @define {boolean} Whether logging is enabled. */ +goog.define('goog.log.ENABLED', goog.debug.LOGGING_ENABLED); + + +/** @const */ +goog.log.ROOT_LOGGER_NAME = goog.debug.Logger.ROOT_LOGGER_NAME; + + + +/** + * @constructor + * @final + */ +goog.log.Logger = goog.debug.Logger; + + + +/** + * @constructor + * @final + */ +goog.log.Level = goog.debug.Logger.Level; + + + +/** + * @constructor + * @final + */ +goog.log.LogRecord = goog.debug.LogRecord; + + +/** + * Finds or creates a logger for a named subsystem. If a logger has already been + * created with the given name it is returned. Otherwise a new logger is + * created. If a new logger is created its log level will be configured based + * on the goog.debug.LogManager configuration and it will configured to also + * send logging output to its parent's handlers. + * @see goog.debug.LogManager + * + * @param {string} name A name for the logger. This should be a dot-separated + * name and should normally be based on the package name or class name of + * the subsystem, such as goog.net.BrowserChannel. + * @param {goog.log.Level=} opt_level If provided, override the + * default logging level with the provided level. + * @return {goog.log.Logger} The named logger or null if logging is disabled. + */ +goog.log.getLogger = function(name, opt_level) { + if (goog.log.ENABLED) { + var logger = goog.debug.LogManager.getLogger(name); + if (opt_level && logger) { + logger.setLevel(opt_level); + } + return logger; + } else { + return null; + } +}; + + +// TODO(johnlenz): try to tighten the types to these functions. +/** + * Adds a handler to the logger. This doesn't use the event system because + * we want to be able to add logging to the event system. + * @param {goog.log.Logger} logger + * @param {Function} handler Handler function to add. + */ +goog.log.addHandler = function(logger, handler) { + if (goog.log.ENABLED && logger) { + logger.addHandler(handler); + } +}; + + +/** + * Removes a handler from the logger. This doesn't use the event system because + * we want to be able to add logging to the event system. + * @param {goog.log.Logger} logger + * @param {Function} handler Handler function to remove. + * @return {boolean} Whether the handler was removed. + */ +goog.log.removeHandler = function(logger, handler) { + if (goog.log.ENABLED && logger) { + return logger.removeHandler(handler); + } else { + return false; + } +}; + + +/** + * Logs a message. If the logger is currently enabled for the + * given message level then the given message is forwarded to all the + * registered output Handler objects. + * @param {goog.log.Logger} logger + * @param {goog.log.Level} level One of the level identifiers. + * @param {goog.debug.Loggable} msg The message to log. + * @param {Error|Object=} opt_exception An exception associated with the + * message. + */ +goog.log.log = function(logger, level, msg, opt_exception) { + if (goog.log.ENABLED && logger) { + logger.log(level, msg, opt_exception); + } +}; + + +/** + * Logs a message at the Level.SEVERE level. + * If the logger is currently enabled for the given message level then the + * given message is forwarded to all the registered output Handler objects. + * @param {goog.log.Logger} logger + * @param {goog.debug.Loggable} msg The message to log. + * @param {Error=} opt_exception An exception associated with the message. + */ +goog.log.error = function(logger, msg, opt_exception) { + if (goog.log.ENABLED && logger) { + logger.severe(msg, opt_exception); + } +}; + + +/** + * Logs a message at the Level.WARNING level. + * If the logger is currently enabled for the given message level then the + * given message is forwarded to all the registered output Handler objects. + * @param {goog.log.Logger} logger + * @param {goog.debug.Loggable} msg The message to log. + * @param {Error=} opt_exception An exception associated with the message. + */ +goog.log.warning = function(logger, msg, opt_exception) { + if (goog.log.ENABLED && logger) { + logger.warning(msg, opt_exception); + } +}; + + +/** + * Logs a message at the Level.INFO level. + * If the logger is currently enabled for the given message level then the + * given message is forwarded to all the registered output Handler objects. + * @param {goog.log.Logger} logger + * @param {goog.debug.Loggable} msg The message to log. + * @param {Error=} opt_exception An exception associated with the message. + */ +goog.log.info = function(logger, msg, opt_exception) { + if (goog.log.ENABLED && logger) { + logger.info(msg, opt_exception); + } +}; + + +/** + * Logs a message at the Level.Fine level. + * If the logger is currently enabled for the given message level then the + * given message is forwarded to all the registered output Handler objects. + * @param {goog.log.Logger} logger + * @param {goog.debug.Loggable} msg The message to log. + * @param {Error=} opt_exception An exception associated with the message. + */ +goog.log.fine = function(logger, msg, opt_exception) { + if (goog.log.ENABLED && logger) { + logger.fine(msg, opt_exception); + } +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/math/box.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/math/box.js new file mode 100644 index 0000000..bae54e4a --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/math/box.js @@ -0,0 +1,388 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A utility class for representing a numeric box. + */ + + +goog.provide('goog.math.Box'); + +goog.require('goog.math.Coordinate'); + + + +/** + * Class for representing a box. A box is specified as a top, right, bottom, + * and left. A box is useful for representing margins and padding. + * + * This class assumes 'screen coordinates': larger Y coordinates are further + * from the top of the screen. + * + * @param {number} top Top. + * @param {number} right Right. + * @param {number} bottom Bottom. + * @param {number} left Left. + * @constructor + */ +goog.math.Box = function(top, right, bottom, left) { + /** + * Top + * @type {number} + */ + this.top = top; + + /** + * Right + * @type {number} + */ + this.right = right; + + /** + * Bottom + * @type {number} + */ + this.bottom = bottom; + + /** + * Left + * @type {number} + */ + this.left = left; +}; + + +/** + * Creates a Box by bounding a collection of goog.math.Coordinate objects + * @param {...goog.math.Coordinate} var_args Coordinates to be included inside + * the box. + * @return {!goog.math.Box} A Box containing all the specified Coordinates. + */ +goog.math.Box.boundingBox = function(var_args) { + var box = new goog.math.Box(arguments[0].y, arguments[0].x, + arguments[0].y, arguments[0].x); + for (var i = 1; i < arguments.length; i++) { + var coord = arguments[i]; + box.top = Math.min(box.top, coord.y); + box.right = Math.max(box.right, coord.x); + box.bottom = Math.max(box.bottom, coord.y); + box.left = Math.min(box.left, coord.x); + } + return box; +}; + + +/** + * @return {number} width The width of this Box. + */ +goog.math.Box.prototype.getWidth = function() { + return this.right - this.left; +}; + + +/** + * @return {number} height The height of this Box. + */ +goog.math.Box.prototype.getHeight = function() { + return this.bottom - this.top; +}; + + +/** + * Creates a copy of the box with the same dimensions. + * @return {!goog.math.Box} A clone of this Box. + */ +goog.math.Box.prototype.clone = function() { + return new goog.math.Box(this.top, this.right, this.bottom, this.left); +}; + + +if (goog.DEBUG) { + /** + * Returns a nice string representing the box. + * @return {string} In the form (50t, 73r, 24b, 13l). + * @override + */ + goog.math.Box.prototype.toString = function() { + return '(' + this.top + 't, ' + this.right + 'r, ' + this.bottom + 'b, ' + + this.left + 'l)'; + }; +} + + +/** + * Returns whether the box contains a coordinate or another box. + * + * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box. + * @return {boolean} Whether the box contains the coordinate or other box. + */ +goog.math.Box.prototype.contains = function(other) { + return goog.math.Box.contains(this, other); +}; + + +/** + * Expands box with the given margins. + * + * @param {number|goog.math.Box} top Top margin or box with all margins. + * @param {number=} opt_right Right margin. + * @param {number=} opt_bottom Bottom margin. + * @param {number=} opt_left Left margin. + * @return {!goog.math.Box} A reference to this Box. + */ +goog.math.Box.prototype.expand = function(top, opt_right, opt_bottom, + opt_left) { + if (goog.isObject(top)) { + this.top -= top.top; + this.right += top.right; + this.bottom += top.bottom; + this.left -= top.left; + } else { + this.top -= top; + this.right += opt_right; + this.bottom += opt_bottom; + this.left -= opt_left; + } + + return this; +}; + + +/** + * Expand this box to include another box. + * NOTE(user): This is used in code that needs to be very fast, please don't + * add functionality to this function at the expense of speed (variable + * arguments, accepting multiple argument types, etc). + * @param {goog.math.Box} box The box to include in this one. + */ +goog.math.Box.prototype.expandToInclude = function(box) { + this.left = Math.min(this.left, box.left); + this.top = Math.min(this.top, box.top); + this.right = Math.max(this.right, box.right); + this.bottom = Math.max(this.bottom, box.bottom); +}; + + +/** + * Compares boxes for equality. + * @param {goog.math.Box} a A Box. + * @param {goog.math.Box} b A Box. + * @return {boolean} True iff the boxes are equal, or if both are null. + */ +goog.math.Box.equals = function(a, b) { + if (a == b) { + return true; + } + if (!a || !b) { + return false; + } + return a.top == b.top && a.right == b.right && + a.bottom == b.bottom && a.left == b.left; +}; + + +/** + * Returns whether a box contains a coordinate or another box. + * + * @param {goog.math.Box} box A Box. + * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box. + * @return {boolean} Whether the box contains the coordinate or other box. + */ +goog.math.Box.contains = function(box, other) { + if (!box || !other) { + return false; + } + + if (other instanceof goog.math.Box) { + return other.left >= box.left && other.right <= box.right && + other.top >= box.top && other.bottom <= box.bottom; + } + + // other is a Coordinate. + return other.x >= box.left && other.x <= box.right && + other.y >= box.top && other.y <= box.bottom; +}; + + +/** + * Returns the relative x position of a coordinate compared to a box. Returns + * zero if the coordinate is inside the box. + * + * @param {goog.math.Box} box A Box. + * @param {goog.math.Coordinate} coord A Coordinate. + * @return {number} The x position of {@code coord} relative to the nearest + * side of {@code box}, or zero if {@code coord} is inside {@code box}. + */ +goog.math.Box.relativePositionX = function(box, coord) { + if (coord.x < box.left) { + return coord.x - box.left; + } else if (coord.x > box.right) { + return coord.x - box.right; + } + return 0; +}; + + +/** + * Returns the relative y position of a coordinate compared to a box. Returns + * zero if the coordinate is inside the box. + * + * @param {goog.math.Box} box A Box. + * @param {goog.math.Coordinate} coord A Coordinate. + * @return {number} The y position of {@code coord} relative to the nearest + * side of {@code box}, or zero if {@code coord} is inside {@code box}. + */ +goog.math.Box.relativePositionY = function(box, coord) { + if (coord.y < box.top) { + return coord.y - box.top; + } else if (coord.y > box.bottom) { + return coord.y - box.bottom; + } + return 0; +}; + + +/** + * Returns the distance between a coordinate and the nearest corner/side of a + * box. Returns zero if the coordinate is inside the box. + * + * @param {goog.math.Box} box A Box. + * @param {goog.math.Coordinate} coord A Coordinate. + * @return {number} The distance between {@code coord} and the nearest + * corner/side of {@code box}, or zero if {@code coord} is inside + * {@code box}. + */ +goog.math.Box.distance = function(box, coord) { + var x = goog.math.Box.relativePositionX(box, coord); + var y = goog.math.Box.relativePositionY(box, coord); + return Math.sqrt(x * x + y * y); +}; + + +/** + * Returns whether two boxes intersect. + * + * @param {goog.math.Box} a A Box. + * @param {goog.math.Box} b A second Box. + * @return {boolean} Whether the boxes intersect. + */ +goog.math.Box.intersects = function(a, b) { + return (a.left <= b.right && b.left <= a.right && + a.top <= b.bottom && b.top <= a.bottom); +}; + + +/** + * Returns whether two boxes would intersect with additional padding. + * + * @param {goog.math.Box} a A Box. + * @param {goog.math.Box} b A second Box. + * @param {number} padding The additional padding. + * @return {boolean} Whether the boxes intersect. + */ +goog.math.Box.intersectsWithPadding = function(a, b, padding) { + return (a.left <= b.right + padding && b.left <= a.right + padding && + a.top <= b.bottom + padding && b.top <= a.bottom + padding); +}; + + +/** + * Rounds the fields to the next larger integer values. + * + * @return {!goog.math.Box} This box with ceil'd fields. + */ +goog.math.Box.prototype.ceil = function() { + this.top = Math.ceil(this.top); + this.right = Math.ceil(this.right); + this.bottom = Math.ceil(this.bottom); + this.left = Math.ceil(this.left); + return this; +}; + + +/** + * Rounds the fields to the next smaller integer values. + * + * @return {!goog.math.Box} This box with floored fields. + */ +goog.math.Box.prototype.floor = function() { + this.top = Math.floor(this.top); + this.right = Math.floor(this.right); + this.bottom = Math.floor(this.bottom); + this.left = Math.floor(this.left); + return this; +}; + + +/** + * Rounds the fields to nearest integer values. + * + * @return {!goog.math.Box} This box with rounded fields. + */ +goog.math.Box.prototype.round = function() { + this.top = Math.round(this.top); + this.right = Math.round(this.right); + this.bottom = Math.round(this.bottom); + this.left = Math.round(this.left); + return this; +}; + + +/** + * Translates this box by the given offsets. If a {@code goog.math.Coordinate} + * is given, then the left and right values are translated by the coordinate's + * x value and the top and bottom values are translated by the coordinate's y + * value. Otherwise, {@code tx} and {@code opt_ty} are used to translate the x + * and y dimension values. + * + * @param {number|goog.math.Coordinate} tx The value to translate the x + * dimension values by or the the coordinate to translate this box by. + * @param {number=} opt_ty The value to translate y dimension values by. + * @return {!goog.math.Box} This box after translating. + */ +goog.math.Box.prototype.translate = function(tx, opt_ty) { + if (tx instanceof goog.math.Coordinate) { + this.left += tx.x; + this.right += tx.x; + this.top += tx.y; + this.bottom += tx.y; + } else { + this.left += tx; + this.right += tx; + if (goog.isNumber(opt_ty)) { + this.top += opt_ty; + this.bottom += opt_ty; + } + } + return this; +}; + + +/** + * Scales this coordinate by the given scale factors. The x and y dimension + * values are scaled by {@code sx} and {@code opt_sy} respectively. + * If {@code opt_sy} is not given, then {@code sx} is used for both x and y. + * + * @param {number} sx The scale factor to use for the x dimension. + * @param {number=} opt_sy The scale factor to use for the y dimension. + * @return {!goog.math.Box} This box after scaling. + */ +goog.math.Box.prototype.scale = function(sx, opt_sy) { + var sy = goog.isNumber(opt_sy) ? opt_sy : sx; + this.left *= sx; + this.right *= sx; + this.top *= sy; + this.bottom *= sy; + return this; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/math/coordinate.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/math/coordinate.js new file mode 100644 index 0000000..bfd1d0c --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/math/coordinate.js @@ -0,0 +1,267 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A utility class for representing two-dimensional positions. + */ + + +goog.provide('goog.math.Coordinate'); + +goog.require('goog.math'); + + + +/** + * Class for representing coordinates and positions. + * @param {number=} opt_x Left, defaults to 0. + * @param {number=} opt_y Top, defaults to 0. + * @constructor + */ +goog.math.Coordinate = function(opt_x, opt_y) { + /** + * X-value + * @type {number} + */ + this.x = goog.isDef(opt_x) ? opt_x : 0; + + /** + * Y-value + * @type {number} + */ + this.y = goog.isDef(opt_y) ? opt_y : 0; +}; + + +/** + * Returns a new copy of the coordinate. + * @return {!goog.math.Coordinate} A clone of this coordinate. + */ +goog.math.Coordinate.prototype.clone = function() { + return new goog.math.Coordinate(this.x, this.y); +}; + + +if (goog.DEBUG) { + /** + * Returns a nice string representing the coordinate. + * @return {string} In the form (50, 73). + * @override + */ + goog.math.Coordinate.prototype.toString = function() { + return '(' + this.x + ', ' + this.y + ')'; + }; +} + + +/** + * Compares coordinates for equality. + * @param {goog.math.Coordinate} a A Coordinate. + * @param {goog.math.Coordinate} b A Coordinate. + * @return {boolean} True iff the coordinates are equal, or if both are null. + */ +goog.math.Coordinate.equals = function(a, b) { + if (a == b) { + return true; + } + if (!a || !b) { + return false; + } + return a.x == b.x && a.y == b.y; +}; + + +/** + * Returns the distance between two coordinates. + * @param {!goog.math.Coordinate} a A Coordinate. + * @param {!goog.math.Coordinate} b A Coordinate. + * @return {number} The distance between {@code a} and {@code b}. + */ +goog.math.Coordinate.distance = function(a, b) { + var dx = a.x - b.x; + var dy = a.y - b.y; + return Math.sqrt(dx * dx + dy * dy); +}; + + +/** + * Returns the magnitude of a coordinate. + * @param {!goog.math.Coordinate} a A Coordinate. + * @return {number} The distance between the origin and {@code a}. + */ +goog.math.Coordinate.magnitude = function(a) { + return Math.sqrt(a.x * a.x + a.y * a.y); +}; + + +/** + * Returns the angle from the origin to a coordinate. + * @param {!goog.math.Coordinate} a A Coordinate. + * @return {number} The angle, in degrees, clockwise from the positive X + * axis to {@code a}. + */ +goog.math.Coordinate.azimuth = function(a) { + return goog.math.angle(0, 0, a.x, a.y); +}; + + +/** + * Returns the squared distance between two coordinates. Squared distances can + * be used for comparisons when the actual value is not required. + * + * Performance note: eliminating the square root is an optimization often used + * in lower-level languages, but the speed difference is not nearly as + * pronounced in JavaScript (only a few percent.) + * + * @param {!goog.math.Coordinate} a A Coordinate. + * @param {!goog.math.Coordinate} b A Coordinate. + * @return {number} The squared distance between {@code a} and {@code b}. + */ +goog.math.Coordinate.squaredDistance = function(a, b) { + var dx = a.x - b.x; + var dy = a.y - b.y; + return dx * dx + dy * dy; +}; + + +/** + * Returns the difference between two coordinates as a new + * goog.math.Coordinate. + * @param {!goog.math.Coordinate} a A Coordinate. + * @param {!goog.math.Coordinate} b A Coordinate. + * @return {!goog.math.Coordinate} A Coordinate representing the difference + * between {@code a} and {@code b}. + */ +goog.math.Coordinate.difference = function(a, b) { + return new goog.math.Coordinate(a.x - b.x, a.y - b.y); +}; + + +/** + * Returns the sum of two coordinates as a new goog.math.Coordinate. + * @param {!goog.math.Coordinate} a A Coordinate. + * @param {!goog.math.Coordinate} b A Coordinate. + * @return {!goog.math.Coordinate} A Coordinate representing the sum of the two + * coordinates. + */ +goog.math.Coordinate.sum = function(a, b) { + return new goog.math.Coordinate(a.x + b.x, a.y + b.y); +}; + + +/** + * Rounds the x and y fields to the next larger integer values. + * @return {!goog.math.Coordinate} This coordinate with ceil'd fields. + */ +goog.math.Coordinate.prototype.ceil = function() { + this.x = Math.ceil(this.x); + this.y = Math.ceil(this.y); + return this; +}; + + +/** + * Rounds the x and y fields to the next smaller integer values. + * @return {!goog.math.Coordinate} This coordinate with floored fields. + */ +goog.math.Coordinate.prototype.floor = function() { + this.x = Math.floor(this.x); + this.y = Math.floor(this.y); + return this; +}; + + +/** + * Rounds the x and y fields to the nearest integer values. + * @return {!goog.math.Coordinate} This coordinate with rounded fields. + */ +goog.math.Coordinate.prototype.round = function() { + this.x = Math.round(this.x); + this.y = Math.round(this.y); + return this; +}; + + +/** + * Translates this box by the given offsets. If a {@code goog.math.Coordinate} + * is given, then the x and y values are translated by the coordinate's x and y. + * Otherwise, x and y are translated by {@code tx} and {@code opt_ty} + * respectively. + * @param {number|goog.math.Coordinate} tx The value to translate x by or the + * the coordinate to translate this coordinate by. + * @param {number=} opt_ty The value to translate y by. + * @return {!goog.math.Coordinate} This coordinate after translating. + */ +goog.math.Coordinate.prototype.translate = function(tx, opt_ty) { + if (tx instanceof goog.math.Coordinate) { + this.x += tx.x; + this.y += tx.y; + } else { + this.x += tx; + if (goog.isNumber(opt_ty)) { + this.y += opt_ty; + } + } + return this; +}; + + +/** + * Scales this coordinate by the given scale factors. The x and y values are + * scaled by {@code sx} and {@code opt_sy} respectively. If {@code opt_sy} + * is not given, then {@code sx} is used for both x and y. + * @param {number} sx The scale factor to use for the x dimension. + * @param {number=} opt_sy The scale factor to use for the y dimension. + * @return {!goog.math.Coordinate} This coordinate after scaling. + */ +goog.math.Coordinate.prototype.scale = function(sx, opt_sy) { + var sy = goog.isNumber(opt_sy) ? opt_sy : sx; + this.x *= sx; + this.y *= sy; + return this; +}; + + +/** + * Rotates this coordinate clockwise about the origin (or, optionally, the given + * center) by the given angle, in radians. + * @param {number} radians The angle by which to rotate this coordinate + * clockwise about the given center, in radians. + * @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults + * to (0, 0) if not given. + */ +goog.math.Coordinate.prototype.rotateRadians = function(radians, opt_center) { + var center = opt_center || new goog.math.Coordinate(0, 0); + + var x = this.x; + var y = this.y; + var cos = Math.cos(radians); + var sin = Math.sin(radians); + + this.x = (x - center.x) * cos - (y - center.y) * sin + center.x; + this.y = (x - center.x) * sin + (y - center.y) * cos + center.y; +}; + + +/** + * Rotates this coordinate clockwise about the origin (or, optionally, the given + * center) by the given angle, in degrees. + * @param {number} degrees The angle by which to rotate this coordinate + * clockwise about the given center, in degrees. + * @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults + * to (0, 0) if not given. + */ +goog.math.Coordinate.prototype.rotateDegrees = function(degrees, opt_center) { + this.rotateRadians(goog.math.toRadians(degrees), opt_center); +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/math/math.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/math/math.js new file mode 100644 index 0000000..ba789dc8 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/math/math.js @@ -0,0 +1,435 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Additional mathematical functions. + */ + +goog.provide('goog.math'); + +goog.require('goog.array'); +goog.require('goog.asserts'); + + +/** + * Returns a random integer greater than or equal to 0 and less than {@code a}. + * @param {number} a The upper bound for the random integer (exclusive). + * @return {number} A random integer N such that 0 <= N < a. + */ +goog.math.randomInt = function(a) { + return Math.floor(Math.random() * a); +}; + + +/** + * Returns a random number greater than or equal to {@code a} and less than + * {@code b}. + * @param {number} a The lower bound for the random number (inclusive). + * @param {number} b The upper bound for the random number (exclusive). + * @return {number} A random number N such that a <= N < b. + */ +goog.math.uniformRandom = function(a, b) { + return a + Math.random() * (b - a); +}; + + +/** + * Takes a number and clamps it to within the provided bounds. + * @param {number} value The input number. + * @param {number} min The minimum value to return. + * @param {number} max The maximum value to return. + * @return {number} The input number if it is within bounds, or the nearest + * number within the bounds. + */ +goog.math.clamp = function(value, min, max) { + return Math.min(Math.max(value, min), max); +}; + + +/** + * The % operator in JavaScript returns the remainder of a / b, but differs from + * some other languages in that the result will have the same sign as the + * dividend. For example, -1 % 8 == -1, whereas in some other languages + * (such as Python) the result would be 7. This function emulates the more + * correct modulo behavior, which is useful for certain applications such as + * calculating an offset index in a circular list. + * + * @param {number} a The dividend. + * @param {number} b The divisor. + * @return {number} a % b where the result is between 0 and b (either 0 <= x < b + * or b < x <= 0, depending on the sign of b). + */ +goog.math.modulo = function(a, b) { + var r = a % b; + // If r and b differ in sign, add b to wrap the result to the correct sign. + return (r * b < 0) ? r + b : r; +}; + + +/** + * Performs linear interpolation between values a and b. Returns the value + * between a and b proportional to x (when x is between 0 and 1. When x is + * outside this range, the return value is a linear extrapolation). + * @param {number} a A number. + * @param {number} b A number. + * @param {number} x The proportion between a and b. + * @return {number} The interpolated value between a and b. + */ +goog.math.lerp = function(a, b, x) { + return a + x * (b - a); +}; + + +/** + * Tests whether the two values are equal to each other, within a certain + * tolerance to adjust for floating point errors. + * @param {number} a A number. + * @param {number} b A number. + * @param {number=} opt_tolerance Optional tolerance range. Defaults + * to 0.000001. If specified, should be greater than 0. + * @return {boolean} Whether {@code a} and {@code b} are nearly equal. + */ +goog.math.nearlyEquals = function(a, b, opt_tolerance) { + return Math.abs(a - b) <= (opt_tolerance || 0.000001); +}; + + +// TODO(user): Rename to normalizeAngle, retaining old name as deprecated +// alias. +/** + * Normalizes an angle to be in range [0-360). Angles outside this range will + * be normalized to be the equivalent angle with that range. + * @param {number} angle Angle in degrees. + * @return {number} Standardized angle. + */ +goog.math.standardAngle = function(angle) { + return goog.math.modulo(angle, 360); +}; + + +/** + * Normalizes an angle to be in range [0-2*PI). Angles outside this range will + * be normalized to be the equivalent angle with that range. + * @param {number} angle Angle in radians. + * @return {number} Standardized angle. + */ +goog.math.standardAngleInRadians = function(angle) { + return goog.math.modulo(angle, 2 * Math.PI); +}; + + +/** + * Converts degrees to radians. + * @param {number} angleDegrees Angle in degrees. + * @return {number} Angle in radians. + */ +goog.math.toRadians = function(angleDegrees) { + return angleDegrees * Math.PI / 180; +}; + + +/** + * Converts radians to degrees. + * @param {number} angleRadians Angle in radians. + * @return {number} Angle in degrees. + */ +goog.math.toDegrees = function(angleRadians) { + return angleRadians * 180 / Math.PI; +}; + + +/** + * For a given angle and radius, finds the X portion of the offset. + * @param {number} degrees Angle in degrees (zero points in +X direction). + * @param {number} radius Radius. + * @return {number} The x-distance for the angle and radius. + */ +goog.math.angleDx = function(degrees, radius) { + return radius * Math.cos(goog.math.toRadians(degrees)); +}; + + +/** + * For a given angle and radius, finds the Y portion of the offset. + * @param {number} degrees Angle in degrees (zero points in +X direction). + * @param {number} radius Radius. + * @return {number} The y-distance for the angle and radius. + */ +goog.math.angleDy = function(degrees, radius) { + return radius * Math.sin(goog.math.toRadians(degrees)); +}; + + +/** + * Computes the angle between two points (x1,y1) and (x2,y2). + * Angle zero points in the +X direction, 90 degrees points in the +Y + * direction (down) and from there we grow clockwise towards 360 degrees. + * @param {number} x1 x of first point. + * @param {number} y1 y of first point. + * @param {number} x2 x of second point. + * @param {number} y2 y of second point. + * @return {number} Standardized angle in degrees of the vector from + * x1,y1 to x2,y2. + */ +goog.math.angle = function(x1, y1, x2, y2) { + return goog.math.standardAngle(goog.math.toDegrees(Math.atan2(y2 - y1, + x2 - x1))); +}; + + +/** + * Computes the difference between startAngle and endAngle (angles in degrees). + * @param {number} startAngle Start angle in degrees. + * @param {number} endAngle End angle in degrees. + * @return {number} The number of degrees that when added to + * startAngle will result in endAngle. Positive numbers mean that the + * direction is clockwise. Negative numbers indicate a counter-clockwise + * direction. + * The shortest route (clockwise vs counter-clockwise) between the angles + * is used. + * When the difference is 180 degrees, the function returns 180 (not -180) + * angleDifference(30, 40) is 10, and angleDifference(40, 30) is -10. + * angleDifference(350, 10) is 20, and angleDifference(10, 350) is -20. + */ +goog.math.angleDifference = function(startAngle, endAngle) { + var d = goog.math.standardAngle(endAngle) - + goog.math.standardAngle(startAngle); + if (d > 180) { + d = d - 360; + } else if (d <= -180) { + d = 360 + d; + } + return d; +}; + + +/** + * Returns the sign of a number as per the "sign" or "signum" function. + * @param {number} x The number to take the sign of. + * @return {number} -1 when negative, 1 when positive, 0 when 0. + */ +goog.math.sign = function(x) { + return x == 0 ? 0 : (x < 0 ? -1 : 1); +}; + + +/** + * JavaScript implementation of Longest Common Subsequence problem. + * http://en.wikipedia.org/wiki/Longest_common_subsequence + * + * Returns the longest possible array that is subarray of both of given arrays. + * + * @param {Array.<Object>} array1 First array of objects. + * @param {Array.<Object>} array2 Second array of objects. + * @param {Function=} opt_compareFn Function that acts as a custom comparator + * for the array ojects. Function should return true if objects are equal, + * otherwise false. + * @param {Function=} opt_collectorFn Function used to decide what to return + * as a result subsequence. It accepts 2 arguments: index of common element + * in the first array and index in the second. The default function returns + * element from the first array. + * @return {!Array.<Object>} A list of objects that are common to both arrays + * such that there is no common subsequence with size greater than the + * length of the list. + */ +goog.math.longestCommonSubsequence = function( + array1, array2, opt_compareFn, opt_collectorFn) { + + var compare = opt_compareFn || function(a, b) { + return a == b; + }; + + var collect = opt_collectorFn || function(i1, i2) { + return array1[i1]; + }; + + var length1 = array1.length; + var length2 = array2.length; + + var arr = []; + for (var i = 0; i < length1 + 1; i++) { + arr[i] = []; + arr[i][0] = 0; + } + + for (var j = 0; j < length2 + 1; j++) { + arr[0][j] = 0; + } + + for (i = 1; i <= length1; i++) { + for (j = 1; j <= length2; j++) { + if (compare(array1[i - 1], array2[j - 1])) { + arr[i][j] = arr[i - 1][j - 1] + 1; + } else { + arr[i][j] = Math.max(arr[i - 1][j], arr[i][j - 1]); + } + } + } + + // Backtracking + var result = []; + var i = length1, j = length2; + while (i > 0 && j > 0) { + if (compare(array1[i - 1], array2[j - 1])) { + result.unshift(collect(i - 1, j - 1)); + i--; + j--; + } else { + if (arr[i - 1][j] > arr[i][j - 1]) { + i--; + } else { + j--; + } + } + } + + return result; +}; + + +/** + * Returns the sum of the arguments. + * @param {...number} var_args Numbers to add. + * @return {number} The sum of the arguments (0 if no arguments were provided, + * {@code NaN} if any of the arguments is not a valid number). + */ +goog.math.sum = function(var_args) { + return /** @type {number} */ (goog.array.reduce(arguments, + function(sum, value) { + return sum + value; + }, 0)); +}; + + +/** + * Returns the arithmetic mean of the arguments. + * @param {...number} var_args Numbers to average. + * @return {number} The average of the arguments ({@code NaN} if no arguments + * were provided or any of the arguments is not a valid number). + */ +goog.math.average = function(var_args) { + return goog.math.sum.apply(null, arguments) / arguments.length; +}; + + +/** + * Returns the unbiased sample variance of the arguments. For a definition, + * see e.g. http://en.wikipedia.org/wiki/Variance + * @param {...number} var_args Number samples to analyze. + * @return {number} The unbiased sample variance of the arguments (0 if fewer + * than two samples were provided, or {@code NaN} if any of the samples is + * not a valid number). + */ +goog.math.sampleVariance = function(var_args) { + var sampleSize = arguments.length; + if (sampleSize < 2) { + return 0; + } + + var mean = goog.math.average.apply(null, arguments); + var variance = goog.math.sum.apply(null, goog.array.map(arguments, + function(val) { + return Math.pow(val - mean, 2); + })) / (sampleSize - 1); + + return variance; +}; + + +/** + * Returns the sample standard deviation of the arguments. For a definition of + * sample standard deviation, see e.g. + * http://en.wikipedia.org/wiki/Standard_deviation + * @param {...number} var_args Number samples to analyze. + * @return {number} The sample standard deviation of the arguments (0 if fewer + * than two samples were provided, or {@code NaN} if any of the samples is + * not a valid number). + */ +goog.math.standardDeviation = function(var_args) { + return Math.sqrt(goog.math.sampleVariance.apply(null, arguments)); +}; + + +/** + * Returns whether the supplied number represents an integer, i.e. that is has + * no fractional component. No range-checking is performed on the number. + * @param {number} num The number to test. + * @return {boolean} Whether {@code num} is an integer. + */ +goog.math.isInt = function(num) { + return isFinite(num) && num % 1 == 0; +}; + + +/** + * Returns whether the supplied number is finite and not NaN. + * @param {number} num The number to test. + * @return {boolean} Whether {@code num} is a finite number. + */ +goog.math.isFiniteNumber = function(num) { + return isFinite(num) && !isNaN(num); +}; + + +/** + * Returns the precise value of floor(log10(num)). + * Simpler implementations didn't work because of floating point rounding + * errors. For example + * <ul> + * <li>Math.floor(Math.log(num) / Math.LN10) is off by one for num == 1e+3. + * <li>Math.floor(Math.log(num) * Math.LOG10E) is off by one for num == 1e+15. + * <li>Math.floor(Math.log10(num)) is off by one for num == 1e+15 - 1. + * </ul> + * @param {number} num A floating point number. + * @return {number} Its logarithm to base 10 rounded down to the nearest + * integer if num > 0. -Infinity if num == 0. NaN if num < 0. + */ +goog.math.log10Floor = function(num) { + if (num > 0) { + var x = Math.round(Math.log(num) * Math.LOG10E); + return x - (parseFloat('1e' + x) > num); + } + return num == 0 ? -Infinity : NaN; +}; + + +/** + * A tweaked variant of {@code Math.floor} which tolerates if the passed number + * is infinitesimally smaller than the closest integer. It often happens with + * the results of floating point calculations because of the finite precision + * of the intermediate results. For example {@code Math.floor(Math.log(1000) / + * Math.LN10) == 2}, not 3 as one would expect. + * @param {number} num A number. + * @param {number=} opt_epsilon An infinitesimally small positive number, the + * rounding error to tolerate. + * @return {number} The largest integer less than or equal to {@code num}. + */ +goog.math.safeFloor = function(num, opt_epsilon) { + goog.asserts.assert(!goog.isDef(opt_epsilon) || opt_epsilon > 0); + return Math.floor(num + (opt_epsilon || 2e-15)); +}; + + +/** + * A tweaked variant of {@code Math.ceil}. See {@code goog.math.safeFloor} for + * details. + * @param {number} num A number. + * @param {number=} opt_epsilon An infinitesimally small positive number, the + * rounding error to tolerate. + * @return {number} The smallest integer greater than or equal to {@code num}. + */ +goog.math.safeCeil = function(num, opt_epsilon) { + goog.asserts.assert(!goog.isDef(opt_epsilon) || opt_epsilon > 0); + return Math.ceil(num - (opt_epsilon || 2e-15)); +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/math/rect.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/math/rect.js new file mode 100644 index 0000000..008918b --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/math/rect.js @@ -0,0 +1,463 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A utility class for representing rectangles. + */ + +goog.provide('goog.math.Rect'); + +goog.require('goog.math.Box'); +goog.require('goog.math.Coordinate'); +goog.require('goog.math.Size'); + + + +/** + * Class for representing rectangular regions. + * @param {number} x Left. + * @param {number} y Top. + * @param {number} w Width. + * @param {number} h Height. + * @constructor + */ +goog.math.Rect = function(x, y, w, h) { + /** @type {number} */ + this.left = x; + + /** @type {number} */ + this.top = y; + + /** @type {number} */ + this.width = w; + + /** @type {number} */ + this.height = h; +}; + + +/** + * @return {!goog.math.Rect} A new copy of this Rectangle. + */ +goog.math.Rect.prototype.clone = function() { + return new goog.math.Rect(this.left, this.top, this.width, this.height); +}; + + +/** + * Returns a new Box object with the same position and dimensions as this + * rectangle. + * @return {!goog.math.Box} A new Box representation of this Rectangle. + */ +goog.math.Rect.prototype.toBox = function() { + var right = this.left + this.width; + var bottom = this.top + this.height; + return new goog.math.Box(this.top, + right, + bottom, + this.left); +}; + + +/** + * Creates a new Rect object with the same position and dimensions as a given + * Box. Note that this is only the inverse of toBox if left/top are defined. + * @param {goog.math.Box} box A box. + * @return {!goog.math.Rect} A new Rect initialized with the box's position + * and size. + */ +goog.math.Rect.createFromBox = function(box) { + return new goog.math.Rect(box.left, box.top, + box.right - box.left, box.bottom - box.top); +}; + + +if (goog.DEBUG) { + /** + * Returns a nice string representing size and dimensions of rectangle. + * @return {string} In the form (50, 73 - 75w x 25h). + * @override + */ + goog.math.Rect.prototype.toString = function() { + return '(' + this.left + ', ' + this.top + ' - ' + this.width + 'w x ' + + this.height + 'h)'; + }; +} + + +/** + * Compares rectangles for equality. + * @param {goog.math.Rect} a A Rectangle. + * @param {goog.math.Rect} b A Rectangle. + * @return {boolean} True iff the rectangles have the same left, top, width, + * and height, or if both are null. + */ +goog.math.Rect.equals = function(a, b) { + if (a == b) { + return true; + } + if (!a || !b) { + return false; + } + return a.left == b.left && a.width == b.width && + a.top == b.top && a.height == b.height; +}; + + +/** + * Computes the intersection of this rectangle and the rectangle parameter. If + * there is no intersection, returns false and leaves this rectangle as is. + * @param {goog.math.Rect} rect A Rectangle. + * @return {boolean} True iff this rectangle intersects with the parameter. + */ +goog.math.Rect.prototype.intersection = function(rect) { + var x0 = Math.max(this.left, rect.left); + var x1 = Math.min(this.left + this.width, rect.left + rect.width); + + if (x0 <= x1) { + var y0 = Math.max(this.top, rect.top); + var y1 = Math.min(this.top + this.height, rect.top + rect.height); + + if (y0 <= y1) { + this.left = x0; + this.top = y0; + this.width = x1 - x0; + this.height = y1 - y0; + + return true; + } + } + return false; +}; + + +/** + * Returns the intersection of two rectangles. Two rectangles intersect if they + * touch at all, for example, two zero width and height rectangles would + * intersect if they had the same top and left. + * @param {goog.math.Rect} a A Rectangle. + * @param {goog.math.Rect} b A Rectangle. + * @return {goog.math.Rect} A new intersection rect (even if width and height + * are 0), or null if there is no intersection. + */ +goog.math.Rect.intersection = function(a, b) { + // There is no nice way to do intersection via a clone, because any such + // clone might be unnecessary if this function returns null. So, we duplicate + // code from above. + + var x0 = Math.max(a.left, b.left); + var x1 = Math.min(a.left + a.width, b.left + b.width); + + if (x0 <= x1) { + var y0 = Math.max(a.top, b.top); + var y1 = Math.min(a.top + a.height, b.top + b.height); + + if (y0 <= y1) { + return new goog.math.Rect(x0, y0, x1 - x0, y1 - y0); + } + } + return null; +}; + + +/** + * Returns whether two rectangles intersect. Two rectangles intersect if they + * touch at all, for example, two zero width and height rectangles would + * intersect if they had the same top and left. + * @param {goog.math.Rect} a A Rectangle. + * @param {goog.math.Rect} b A Rectangle. + * @return {boolean} Whether a and b intersect. + */ +goog.math.Rect.intersects = function(a, b) { + return (a.left <= b.left + b.width && b.left <= a.left + a.width && + a.top <= b.top + b.height && b.top <= a.top + a.height); +}; + + +/** + * Returns whether a rectangle intersects this rectangle. + * @param {goog.math.Rect} rect A rectangle. + * @return {boolean} Whether rect intersects this rectangle. + */ +goog.math.Rect.prototype.intersects = function(rect) { + return goog.math.Rect.intersects(this, rect); +}; + + +/** + * Computes the difference regions between two rectangles. The return value is + * an array of 0 to 4 rectangles defining the remaining regions of the first + * rectangle after the second has been subtracted. + * @param {goog.math.Rect} a A Rectangle. + * @param {goog.math.Rect} b A Rectangle. + * @return {!Array.<!goog.math.Rect>} An array with 0 to 4 rectangles which + * together define the difference area of rectangle a minus rectangle b. + */ +goog.math.Rect.difference = function(a, b) { + var intersection = goog.math.Rect.intersection(a, b); + if (!intersection || !intersection.height || !intersection.width) { + return [a.clone()]; + } + + var result = []; + + var top = a.top; + var height = a.height; + + var ar = a.left + a.width; + var ab = a.top + a.height; + + var br = b.left + b.width; + var bb = b.top + b.height; + + // Subtract off any area on top where A extends past B + if (b.top > a.top) { + result.push(new goog.math.Rect(a.left, a.top, a.width, b.top - a.top)); + top = b.top; + // If we're moving the top down, we also need to subtract the height diff. + height -= b.top - a.top; + } + // Subtract off any area on bottom where A extends past B + if (bb < ab) { + result.push(new goog.math.Rect(a.left, bb, a.width, ab - bb)); + height = bb - top; + } + // Subtract any area on left where A extends past B + if (b.left > a.left) { + result.push(new goog.math.Rect(a.left, top, b.left - a.left, height)); + } + // Subtract any area on right where A extends past B + if (br < ar) { + result.push(new goog.math.Rect(br, top, ar - br, height)); + } + + return result; +}; + + +/** + * Computes the difference regions between this rectangle and {@code rect}. The + * return value is an array of 0 to 4 rectangles defining the remaining regions + * of this rectangle after the other has been subtracted. + * @param {goog.math.Rect} rect A Rectangle. + * @return {!Array.<!goog.math.Rect>} An array with 0 to 4 rectangles which + * together define the difference area of rectangle a minus rectangle b. + */ +goog.math.Rect.prototype.difference = function(rect) { + return goog.math.Rect.difference(this, rect); +}; + + +/** + * Expand this rectangle to also include the area of the given rectangle. + * @param {goog.math.Rect} rect The other rectangle. + */ +goog.math.Rect.prototype.boundingRect = function(rect) { + // We compute right and bottom before we change left and top below. + var right = Math.max(this.left + this.width, rect.left + rect.width); + var bottom = Math.max(this.top + this.height, rect.top + rect.height); + + this.left = Math.min(this.left, rect.left); + this.top = Math.min(this.top, rect.top); + + this.width = right - this.left; + this.height = bottom - this.top; +}; + + +/** + * Returns a new rectangle which completely contains both input rectangles. + * @param {goog.math.Rect} a A rectangle. + * @param {goog.math.Rect} b A rectangle. + * @return {goog.math.Rect} A new bounding rect, or null if either rect is + * null. + */ +goog.math.Rect.boundingRect = function(a, b) { + if (!a || !b) { + return null; + } + + var clone = a.clone(); + clone.boundingRect(b); + + return clone; +}; + + +/** + * Tests whether this rectangle entirely contains another rectangle or + * coordinate. + * + * @param {goog.math.Rect|goog.math.Coordinate} another The rectangle or + * coordinate to test for containment. + * @return {boolean} Whether this rectangle contains given rectangle or + * coordinate. + */ +goog.math.Rect.prototype.contains = function(another) { + if (another instanceof goog.math.Rect) { + return this.left <= another.left && + this.left + this.width >= another.left + another.width && + this.top <= another.top && + this.top + this.height >= another.top + another.height; + } else { // (another instanceof goog.math.Coordinate) + return another.x >= this.left && + another.x <= this.left + this.width && + another.y >= this.top && + another.y <= this.top + this.height; + } +}; + + +/** + * @param {!goog.math.Coordinate} point A coordinate. + * @return {number} The squared distance between the point and the closest + * point inside the rectangle. Returns 0 if the point is inside the + * rectangle. + */ +goog.math.Rect.prototype.squaredDistance = function(point) { + var dx = point.x < this.left ? + this.left - point.x : Math.max(point.x - (this.left + this.width), 0); + var dy = point.y < this.top ? + this.top - point.y : Math.max(point.y - (this.top + this.height), 0); + return dx * dx + dy * dy; +}; + + +/** + * @param {!goog.math.Coordinate} point A coordinate. + * @return {number} The distance between the point and the closest point + * inside the rectangle. Returns 0 if the point is inside the rectangle. + */ +goog.math.Rect.prototype.distance = function(point) { + return Math.sqrt(this.squaredDistance(point)); +}; + + +/** + * @return {!goog.math.Size} The size of this rectangle. + */ +goog.math.Rect.prototype.getSize = function() { + return new goog.math.Size(this.width, this.height); +}; + + +/** + * @return {!goog.math.Coordinate} A new coordinate for the top-left corner of + * the rectangle. + */ +goog.math.Rect.prototype.getTopLeft = function() { + return new goog.math.Coordinate(this.left, this.top); +}; + + +/** + * @return {!goog.math.Coordinate} A new coordinate for the center of the + * rectangle. + */ +goog.math.Rect.prototype.getCenter = function() { + return new goog.math.Coordinate( + this.left + this.width / 2, this.top + this.height / 2); +}; + + +/** + * @return {!goog.math.Coordinate} A new coordinate for the bottom-right corner + * of the rectangle. + */ +goog.math.Rect.prototype.getBottomRight = function() { + return new goog.math.Coordinate( + this.left + this.width, this.top + this.height); +}; + + +/** + * Rounds the fields to the next larger integer values. + * @return {!goog.math.Rect} This rectangle with ceil'd fields. + */ +goog.math.Rect.prototype.ceil = function() { + this.left = Math.ceil(this.left); + this.top = Math.ceil(this.top); + this.width = Math.ceil(this.width); + this.height = Math.ceil(this.height); + return this; +}; + + +/** + * Rounds the fields to the next smaller integer values. + * @return {!goog.math.Rect} This rectangle with floored fields. + */ +goog.math.Rect.prototype.floor = function() { + this.left = Math.floor(this.left); + this.top = Math.floor(this.top); + this.width = Math.floor(this.width); + this.height = Math.floor(this.height); + return this; +}; + + +/** + * Rounds the fields to nearest integer values. + * @return {!goog.math.Rect} This rectangle with rounded fields. + */ +goog.math.Rect.prototype.round = function() { + this.left = Math.round(this.left); + this.top = Math.round(this.top); + this.width = Math.round(this.width); + this.height = Math.round(this.height); + return this; +}; + + +/** + * Translates this rectangle by the given offsets. If a + * {@code goog.math.Coordinate} is given, then the left and top values are + * translated by the coordinate's x and y values. Otherwise, top and left are + * translated by {@code tx} and {@code opt_ty} respectively. + * @param {number|goog.math.Coordinate} tx The value to translate left by or the + * the coordinate to translate this rect by. + * @param {number=} opt_ty The value to translate top by. + * @return {!goog.math.Rect} This rectangle after translating. + */ +goog.math.Rect.prototype.translate = function(tx, opt_ty) { + if (tx instanceof goog.math.Coordinate) { + this.left += tx.x; + this.top += tx.y; + } else { + this.left += tx; + if (goog.isNumber(opt_ty)) { + this.top += opt_ty; + } + } + return this; +}; + + +/** + * Scales this rectangle by the given scale factors. The left and width values + * are scaled by {@code sx} and the top and height values are scaled by + * {@code opt_sy}. If {@code opt_sy} is not given, then all fields are scaled + * by {@code sx}. + * @param {number} sx The scale factor to use for the x dimension. + * @param {number=} opt_sy The scale factor to use for the y dimension. + * @return {!goog.math.Rect} This rectangle after scaling. + */ +goog.math.Rect.prototype.scale = function(sx, opt_sy) { + var sy = goog.isNumber(opt_sy) ? opt_sy : sx; + this.left *= sx; + this.width *= sx; + this.top *= sy; + this.height *= sy; + return this; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/math/size.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/math/size.js new file mode 100644 index 0000000..1b3841b --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/math/size.js @@ -0,0 +1,206 @@ +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A utility class for representing two-dimensional sizes. + */ + + +goog.provide('goog.math.Size'); + + + +/** + * Class for representing sizes consisting of a width and height. Undefined + * width and height support is deprecated and results in compiler warning. + * @param {number} width Width. + * @param {number} height Height. + * @constructor + */ +goog.math.Size = function(width, height) { + /** + * Width + * @type {number} + */ + this.width = width; + + /** + * Height + * @type {number} + */ + this.height = height; +}; + + +/** + * Compares sizes for equality. + * @param {goog.math.Size} a A Size. + * @param {goog.math.Size} b A Size. + * @return {boolean} True iff the sizes have equal widths and equal + * heights, or if both are null. + */ +goog.math.Size.equals = function(a, b) { + if (a == b) { + return true; + } + if (!a || !b) { + return false; + } + return a.width == b.width && a.height == b.height; +}; + + +/** + * @return {!goog.math.Size} A new copy of the Size. + */ +goog.math.Size.prototype.clone = function() { + return new goog.math.Size(this.width, this.height); +}; + + +if (goog.DEBUG) { + /** + * Returns a nice string representing size. + * @return {string} In the form (50 x 73). + * @override + */ + goog.math.Size.prototype.toString = function() { + return '(' + this.width + ' x ' + this.height + ')'; + }; +} + + +/** + * @return {number} The longer of the two dimensions in the size. + */ +goog.math.Size.prototype.getLongest = function() { + return Math.max(this.width, this.height); +}; + + +/** + * @return {number} The shorter of the two dimensions in the size. + */ +goog.math.Size.prototype.getShortest = function() { + return Math.min(this.width, this.height); +}; + + +/** + * @return {number} The area of the size (width * height). + */ +goog.math.Size.prototype.area = function() { + return this.width * this.height; +}; + + +/** + * @return {number} The perimeter of the size (width + height) * 2. + */ +goog.math.Size.prototype.perimeter = function() { + return (this.width + this.height) * 2; +}; + + +/** + * @return {number} The ratio of the size's width to its height. + */ +goog.math.Size.prototype.aspectRatio = function() { + return this.width / this.height; +}; + + +/** + * @return {boolean} True if the size has zero area, false if both dimensions + * are non-zero numbers. + */ +goog.math.Size.prototype.isEmpty = function() { + return !this.area(); +}; + + +/** + * Clamps the width and height parameters upward to integer values. + * @return {!goog.math.Size} This size with ceil'd components. + */ +goog.math.Size.prototype.ceil = function() { + this.width = Math.ceil(this.width); + this.height = Math.ceil(this.height); + return this; +}; + + +/** + * @param {!goog.math.Size} target The target size. + * @return {boolean} True if this Size is the same size or smaller than the + * target size in both dimensions. + */ +goog.math.Size.prototype.fitsInside = function(target) { + return this.width <= target.width && this.height <= target.height; +}; + + +/** + * Clamps the width and height parameters downward to integer values. + * @return {!goog.math.Size} This size with floored components. + */ +goog.math.Size.prototype.floor = function() { + this.width = Math.floor(this.width); + this.height = Math.floor(this.height); + return this; +}; + + +/** + * Rounds the width and height parameters to integer values. + * @return {!goog.math.Size} This size with rounded components. + */ +goog.math.Size.prototype.round = function() { + this.width = Math.round(this.width); + this.height = Math.round(this.height); + return this; +}; + + +/** + * Scales this size by the given scale factors. The width and height are scaled + * by {@code sx} and {@code opt_sy} respectively. If {@code opt_sy} is not + * given, then {@code sx} is used for both the width and height. + * @param {number} sx The scale factor to use for the width. + * @param {number=} opt_sy The scale factor to use for the height. + * @return {!goog.math.Size} This Size object after scaling. + */ +goog.math.Size.prototype.scale = function(sx, opt_sy) { + var sy = goog.isNumber(opt_sy) ? opt_sy : sx; + this.width *= sx; + this.height *= sy; + return this; +}; + + +/** + * Uniformly scales the size to fit inside the dimensions of a given size. The + * original aspect ratio will be preserved. + * + * This function assumes that both Sizes contain strictly positive dimensions. + * @param {!goog.math.Size} target The target size. + * @return {!goog.math.Size} This Size object, after optional scaling. + */ +goog.math.Size.prototype.scaleToFit = function(target) { + var s = this.aspectRatio() > target.aspectRatio() ? + target.width / this.width : + target.height / this.height; + + return this.scale(s); +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/net/jsloader.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/net/jsloader.js new file mode 100644 index 0000000..26b025f --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/net/jsloader.js @@ -0,0 +1,367 @@ +// Copyright 2011 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A utility to load JavaScript files via DOM script tags. + * Refactored from goog.net.Jsonp. Works cross-domain. + * + */ + +goog.provide('goog.net.jsloader'); +goog.provide('goog.net.jsloader.Error'); +goog.provide('goog.net.jsloader.ErrorCode'); +goog.provide('goog.net.jsloader.Options'); + +goog.require('goog.array'); +goog.require('goog.async.Deferred'); +goog.require('goog.debug.Error'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); + + +/** + * The name of the property of goog.global under which the JavaScript + * verification object is stored by the loaded script. + * @type {string} + * @private + */ +goog.net.jsloader.GLOBAL_VERIFY_OBJS_ = 'closure_verification'; + + +/** + * The default length of time, in milliseconds, we are prepared to wait for a + * load request to complete. + * @type {number} + */ +goog.net.jsloader.DEFAULT_TIMEOUT = 5000; + + +/** + * Optional parameters for goog.net.jsloader.send. + * timeout: The length of time, in milliseconds, we are prepared to wait + * for a load request to complete. Default it 5 seconds. + * document: The HTML document under which to load the JavaScript. Default is + * the current document. + * cleanupWhenDone: If true clean up the script tag after script completes to + * load. This is important if you just want to read data from the JavaScript + * and then throw it away. Default is false. + * + * @typedef {{ + * timeout: (number|undefined), + * document: (HTMLDocument|undefined), + * cleanupWhenDone: (boolean|undefined) + * }} + */ +goog.net.jsloader.Options; + + +/** + * Scripts (URIs) waiting to be loaded. + * @type {Array.<string>} + * @private + */ +goog.net.jsloader.scriptsToLoad_ = []; + + +/** + * Loads and evaluates the JavaScript files at the specified URIs, guaranteeing + * the order of script loads. + * + * Because we have to load the scripts in serial (load script 1, exec script 1, + * load script 2, exec script 2, and so on), this will be slower than doing + * the network fetches in parallel. + * + * If you need to load a large number of scripts but dependency order doesn't + * matter, you should just call goog.net.jsloader.load N times. + * + * If you need to load a large number of scripts on the same domain, + * you may want to use goog.module.ModuleLoader. + * + * @param {Array.<string>} uris The URIs to load. + * @param {goog.net.jsloader.Options=} opt_options Optional parameters. See + * goog.net.jsloader.options documentation for details. + */ +goog.net.jsloader.loadMany = function(uris, opt_options) { + // Loading the scripts in serial introduces asynchronosity into the flow. + // Therefore, there are race conditions where client A can kick off the load + // sequence for client B, even though client A's scripts haven't all been + // loaded yet. + // + // To work around this issue, all module loads share a queue. + if (!uris.length) { + return; + } + + var isAnotherModuleLoading = goog.net.jsloader.scriptsToLoad_.length; + goog.array.extend(goog.net.jsloader.scriptsToLoad_, uris); + if (isAnotherModuleLoading) { + // jsloader is still loading some other scripts. + // In order to prevent the race condition noted above, we just add + // these URIs to the end of the scripts' queue and return. + return; + } + + uris = goog.net.jsloader.scriptsToLoad_; + var popAndLoadNextScript = function() { + var uri = uris.shift(); + var deferred = goog.net.jsloader.load(uri, opt_options); + if (uris.length) { + deferred.addBoth(popAndLoadNextScript); + } + }; + popAndLoadNextScript(); +}; + + +/** + * Loads and evaluates a JavaScript file. + * When the script loads, a user callback is called. + * It is the client's responsibility to verify that the script ran successfully. + * + * @param {string} uri The URI of the JavaScript. + * @param {goog.net.jsloader.Options=} opt_options Optional parameters. See + * goog.net.jsloader.Options documentation for details. + * @return {!goog.async.Deferred} The deferred result, that may be used to add + * callbacks and/or cancel the transmission. + * The error callback will be called with a single goog.net.jsloader.Error + * parameter. + */ +goog.net.jsloader.load = function(uri, opt_options) { + var options = opt_options || {}; + var doc = options.document || document; + + var script = goog.dom.createElement(goog.dom.TagName.SCRIPT); + var request = {script_: script, timeout_: undefined}; + var deferred = new goog.async.Deferred(goog.net.jsloader.cancel_, request); + + // Set a timeout. + var timeout = null; + var timeoutDuration = goog.isDefAndNotNull(options.timeout) ? + options.timeout : goog.net.jsloader.DEFAULT_TIMEOUT; + if (timeoutDuration > 0) { + timeout = window.setTimeout(function() { + goog.net.jsloader.cleanup_(script, true); + deferred.errback(new goog.net.jsloader.Error( + goog.net.jsloader.ErrorCode.TIMEOUT, + 'Timeout reached for loading script ' + uri)); + }, timeoutDuration); + request.timeout_ = timeout; + } + + // Hang the user callback to be called when the script completes to load. + // NOTE(user): This callback will be called in IE even upon error. In any + // case it is the client's responsibility to verify that the script ran + // successfully. + script.onload = script.onreadystatechange = function() { + if (!script.readyState || script.readyState == 'loaded' || + script.readyState == 'complete') { + var removeScriptNode = options.cleanupWhenDone || false; + goog.net.jsloader.cleanup_(script, removeScriptNode, timeout); + deferred.callback(null); + } + }; + + // Add an error callback. + // NOTE(user): Not supported in IE. + script.onerror = function() { + goog.net.jsloader.cleanup_(script, true, timeout); + deferred.errback(new goog.net.jsloader.Error( + goog.net.jsloader.ErrorCode.LOAD_ERROR, + 'Error while loading script ' + uri)); + }; + + // Add the script element to the document. + goog.dom.setProperties(script, { + 'type': 'text/javascript', + 'charset': 'UTF-8', + // NOTE(user): Safari never loads the script if we don't set + // the src attribute before appending. + 'src': uri + }); + var scriptParent = goog.net.jsloader.getScriptParentElement_(doc); + scriptParent.appendChild(script); + + return deferred; +}; + + +/** + * Loads a JavaScript file and verifies it was evaluated successfully, using a + * verification object. + * The verification object is set by the loaded JavaScript at the end of the + * script. + * We verify this object was set and return its value in the success callback. + * If the object is not defined we trigger an error callback. + * + * @param {string} uri The URI of the JavaScript. + * @param {string} verificationObjName The name of the verification object that + * the loaded script should set. + * @param {goog.net.jsloader.Options} options Optional parameters. See + * goog.net.jsloader.Options documentation for details. + * @return {!goog.async.Deferred} The deferred result, that may be used to add + * callbacks and/or cancel the transmission. + * The success callback will be called with a single parameter containing + * the value of the verification object. + * The error callback will be called with a single goog.net.jsloader.Error + * parameter. + */ +goog.net.jsloader.loadAndVerify = function(uri, verificationObjName, options) { + // Define the global objects variable. + if (!goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_]) { + goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_] = {}; + } + var verifyObjs = goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_]; + + // Verify that the expected object does not exist yet. + if (goog.isDef(verifyObjs[verificationObjName])) { + // TODO(user): Error or reset variable? + return goog.async.Deferred.fail(new goog.net.jsloader.Error( + goog.net.jsloader.ErrorCode.VERIFY_OBJECT_ALREADY_EXISTS, + 'Verification object ' + verificationObjName + ' already defined.')); + } + + // Send request to load the JavaScript. + var sendDeferred = goog.net.jsloader.load(uri, options); + + // Create a deferred object wrapping the send result. + var deferred = new goog.async.Deferred( + goog.bind(sendDeferred.cancel, sendDeferred)); + + // Call user back with object that was set by the script. + sendDeferred.addCallback(function() { + var result = verifyObjs[verificationObjName]; + if (goog.isDef(result)) { + deferred.callback(result); + delete verifyObjs[verificationObjName]; + } else { + // Error: script was not loaded properly. + deferred.errback(new goog.net.jsloader.Error( + goog.net.jsloader.ErrorCode.VERIFY_ERROR, + 'Script ' + uri + ' loaded, but verification object ' + + verificationObjName + ' was not defined.')); + } + }); + + // Pass error to new deferred object. + sendDeferred.addErrback(function(error) { + if (goog.isDef(verifyObjs[verificationObjName])) { + delete verifyObjs[verificationObjName]; + } + deferred.errback(error); + }); + + return deferred; +}; + + +/** + * Gets the DOM element under which we should add new script elements. + * How? Take the first head element, and if not found take doc.documentElement, + * which always exists. + * + * @param {!HTMLDocument} doc The relevant document. + * @return {!Element} The script parent element. + * @private + */ +goog.net.jsloader.getScriptParentElement_ = function(doc) { + var headElements = doc.getElementsByTagName(goog.dom.TagName.HEAD); + if (!headElements || goog.array.isEmpty(headElements)) { + return doc.documentElement; + } else { + return headElements[0]; + } +}; + + +/** + * Cancels a given request. + * @this {{script_: Element, timeout_: number}} The request context. + * @private + */ +goog.net.jsloader.cancel_ = function() { + var request = this; + if (request && request.script_) { + var scriptNode = request.script_; + if (scriptNode && scriptNode.tagName == 'SCRIPT') { + goog.net.jsloader.cleanup_(scriptNode, true, request.timeout_); + } + } +}; + + +/** + * Removes the script node and the timeout. + * + * @param {Node} scriptNode The node to be cleaned up. + * @param {boolean} removeScriptNode If true completely remove the script node. + * @param {?number=} opt_timeout The timeout handler to cleanup. + * @private + */ +goog.net.jsloader.cleanup_ = function(scriptNode, removeScriptNode, + opt_timeout) { + if (goog.isDefAndNotNull(opt_timeout)) { + goog.global.clearTimeout(opt_timeout); + } + + scriptNode.onload = goog.nullFunction; + scriptNode.onerror = goog.nullFunction; + scriptNode.onreadystatechange = goog.nullFunction; + + // Do this after a delay (removing the script node of a running script can + // confuse older IEs). + if (removeScriptNode) { + window.setTimeout(function() { + goog.dom.removeNode(scriptNode); + }, 0); + } +}; + + +/** + * Possible error codes for jsloader. + * @enum {number} + */ +goog.net.jsloader.ErrorCode = { + LOAD_ERROR: 0, + TIMEOUT: 1, + VERIFY_ERROR: 2, + VERIFY_OBJECT_ALREADY_EXISTS: 3 +}; + + + +/** + * A jsloader error. + * + * @param {goog.net.jsloader.ErrorCode} code The error code. + * @param {string=} opt_message Additional message. + * @constructor + * @extends {goog.debug.Error} + * @final + */ +goog.net.jsloader.Error = function(code, opt_message) { + var msg = 'Jsloader error (code #' + code + ')'; + if (opt_message) { + msg += ': ' + opt_message; + } + goog.net.jsloader.Error.base(this, 'constructor', msg); + + /** + * The code for this error. + * + * @type {goog.net.jsloader.ErrorCode} + */ + this.code = code; +}; +goog.inherits(goog.net.jsloader.Error, goog.debug.Error); diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/object/object.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/object/object.js new file mode 100644 index 0000000..e181ffb --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/object/object.js @@ -0,0 +1,637 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utilities for manipulating objects/maps/hashes. + */ + +goog.provide('goog.object'); + + +/** + * Calls a function for each element in an object/map/hash. + * + * @param {Object.<K,V>} obj The object over which to iterate. + * @param {function(this:T,V,?,Object.<K,V>):?} f The function to call + * for every element. This function takes 3 arguments (the element, the + * index and the object) and the return value is ignored. + * @param {T=} opt_obj This is used as the 'this' object within f. + * @template T,K,V + */ +goog.object.forEach = function(obj, f, opt_obj) { + for (var key in obj) { + f.call(opt_obj, obj[key], key, obj); + } +}; + + +/** + * Calls a function for each element in an object/map/hash. If that call returns + * true, adds the element to a new object. + * + * @param {Object.<K,V>} obj The object over which to iterate. + * @param {function(this:T,V,?,Object.<K,V>):boolean} f The function to call + * for every element. This + * function takes 3 arguments (the element, the index and the object) + * and should return a boolean. If the return value is true the + * element is added to the result object. If it is false the + * element is not included. + * @param {T=} opt_obj This is used as the 'this' object within f. + * @return {!Object.<K,V>} a new object in which only elements that passed the + * test are present. + * @template T,K,V + */ +goog.object.filter = function(obj, f, opt_obj) { + var res = {}; + for (var key in obj) { + if (f.call(opt_obj, obj[key], key, obj)) { + res[key] = obj[key]; + } + } + return res; +}; + + +/** + * For every element in an object/map/hash calls a function and inserts the + * result into a new object. + * + * @param {Object.<K,V>} obj The object over which to iterate. + * @param {function(this:T,V,?,Object.<K,V>):R} f The function to call + * for every element. This function + * takes 3 arguments (the element, the index and the object) + * and should return something. The result will be inserted + * into a new object. + * @param {T=} opt_obj This is used as the 'this' object within f. + * @return {!Object.<K,R>} a new object with the results from f. + * @template T,K,V,R + */ +goog.object.map = function(obj, f, opt_obj) { + var res = {}; + for (var key in obj) { + res[key] = f.call(opt_obj, obj[key], key, obj); + } + return res; +}; + + +/** + * Calls a function for each element in an object/map/hash. If any + * call returns true, returns true (without checking the rest). If + * all calls return false, returns false. + * + * @param {Object.<K,V>} obj The object to check. + * @param {function(this:T,V,?,Object.<K,V>):boolean} f The function to + * call for every element. This function + * takes 3 arguments (the element, the index and the object) and should + * return a boolean. + * @param {T=} opt_obj This is used as the 'this' object within f. + * @return {boolean} true if any element passes the test. + * @template T,K,V + */ +goog.object.some = function(obj, f, opt_obj) { + for (var key in obj) { + if (f.call(opt_obj, obj[key], key, obj)) { + return true; + } + } + return false; +}; + + +/** + * Calls a function for each element in an object/map/hash. If + * all calls return true, returns true. If any call returns false, returns + * false at this point and does not continue to check the remaining elements. + * + * @param {Object.<K,V>} obj The object to check. + * @param {?function(this:T,V,?,Object.<K,V>):boolean} f The function to + * call for every element. This function + * takes 3 arguments (the element, the index and the object) and should + * return a boolean. + * @param {T=} opt_obj This is used as the 'this' object within f. + * @return {boolean} false if any element fails the test. + * @template T,K,V + */ +goog.object.every = function(obj, f, opt_obj) { + for (var key in obj) { + if (!f.call(opt_obj, obj[key], key, obj)) { + return false; + } + } + return true; +}; + + +/** + * Returns the number of key-value pairs in the object map. + * + * @param {Object} obj The object for which to get the number of key-value + * pairs. + * @return {number} The number of key-value pairs in the object map. + */ +goog.object.getCount = function(obj) { + // JS1.5 has __count__ but it has been deprecated so it raises a warning... + // in other words do not use. Also __count__ only includes the fields on the + // actual object and not in the prototype chain. + var rv = 0; + for (var key in obj) { + rv++; + } + return rv; +}; + + +/** + * Returns one key from the object map, if any exists. + * For map literals the returned key will be the first one in most of the + * browsers (a know exception is Konqueror). + * + * @param {Object} obj The object to pick a key from. + * @return {string|undefined} The key or undefined if the object is empty. + */ +goog.object.getAnyKey = function(obj) { + for (var key in obj) { + return key; + } +}; + + +/** + * Returns one value from the object map, if any exists. + * For map literals the returned value will be the first one in most of the + * browsers (a know exception is Konqueror). + * + * @param {Object.<K,V>} obj The object to pick a value from. + * @return {V|undefined} The value or undefined if the object is empty. + * @template K,V + */ +goog.object.getAnyValue = function(obj) { + for (var key in obj) { + return obj[key]; + } +}; + + +/** + * Whether the object/hash/map contains the given object as a value. + * An alias for goog.object.containsValue(obj, val). + * + * @param {Object.<K,V>} obj The object in which to look for val. + * @param {V} val The object for which to check. + * @return {boolean} true if val is present. + * @template K,V + */ +goog.object.contains = function(obj, val) { + return goog.object.containsValue(obj, val); +}; + + +/** + * Returns the values of the object/map/hash. + * + * @param {Object.<K,V>} obj The object from which to get the values. + * @return {!Array.<V>} The values in the object/map/hash. + * @template K,V + */ +goog.object.getValues = function(obj) { + var res = []; + var i = 0; + for (var key in obj) { + res[i++] = obj[key]; + } + return res; +}; + + +/** + * Returns the keys of the object/map/hash. + * + * @param {Object} obj The object from which to get the keys. + * @return {!Array.<string>} Array of property keys. + */ +goog.object.getKeys = function(obj) { + var res = []; + var i = 0; + for (var key in obj) { + res[i++] = key; + } + return res; +}; + + +/** + * Get a value from an object multiple levels deep. This is useful for + * pulling values from deeply nested objects, such as JSON responses. + * Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3) + * + * @param {!Object} obj An object to get the value from. Can be array-like. + * @param {...(string|number|!Array.<number|string>)} var_args A number of keys + * (as strings, or numbers, for array-like objects). Can also be + * specified as a single array of keys. + * @return {*} The resulting value. If, at any point, the value for a key + * is undefined, returns undefined. + */ +goog.object.getValueByKeys = function(obj, var_args) { + var isArrayLike = goog.isArrayLike(var_args); + var keys = isArrayLike ? var_args : arguments; + + // Start with the 2nd parameter for the variable parameters syntax. + for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) { + obj = obj[keys[i]]; + if (!goog.isDef(obj)) { + break; + } + } + + return obj; +}; + + +/** + * Whether the object/map/hash contains the given key. + * + * @param {Object} obj The object in which to look for key. + * @param {*} key The key for which to check. + * @return {boolean} true If the map contains the key. + */ +goog.object.containsKey = function(obj, key) { + return key in obj; +}; + + +/** + * Whether the object/map/hash contains the given value. This is O(n). + * + * @param {Object.<K,V>} obj The object in which to look for val. + * @param {V} val The value for which to check. + * @return {boolean} true If the map contains the value. + * @template K,V + */ +goog.object.containsValue = function(obj, val) { + for (var key in obj) { + if (obj[key] == val) { + return true; + } + } + return false; +}; + + +/** + * Searches an object for an element that satisfies the given condition and + * returns its key. + * @param {Object.<K,V>} obj The object to search in. + * @param {function(this:T,V,string,Object.<K,V>):boolean} f The + * function to call for every element. Takes 3 arguments (the value, + * the key and the object) and should return a boolean. + * @param {T=} opt_this An optional "this" context for the function. + * @return {string|undefined} The key of an element for which the function + * returns true or undefined if no such element is found. + * @template T,K,V + */ +goog.object.findKey = function(obj, f, opt_this) { + for (var key in obj) { + if (f.call(opt_this, obj[key], key, obj)) { + return key; + } + } + return undefined; +}; + + +/** + * Searches an object for an element that satisfies the given condition and + * returns its value. + * @param {Object.<K,V>} obj The object to search in. + * @param {function(this:T,V,string,Object.<K,V>):boolean} f The function + * to call for every element. Takes 3 arguments (the value, the key + * and the object) and should return a boolean. + * @param {T=} opt_this An optional "this" context for the function. + * @return {V} The value of an element for which the function returns true or + * undefined if no such element is found. + * @template T,K,V + */ +goog.object.findValue = function(obj, f, opt_this) { + var key = goog.object.findKey(obj, f, opt_this); + return key && obj[key]; +}; + + +/** + * Whether the object/map/hash is empty. + * + * @param {Object} obj The object to test. + * @return {boolean} true if obj is empty. + */ +goog.object.isEmpty = function(obj) { + for (var key in obj) { + return false; + } + return true; +}; + + +/** + * Removes all key value pairs from the object/map/hash. + * + * @param {Object} obj The object to clear. + */ +goog.object.clear = function(obj) { + for (var i in obj) { + delete obj[i]; + } +}; + + +/** + * Removes a key-value pair based on the key. + * + * @param {Object} obj The object from which to remove the key. + * @param {*} key The key to remove. + * @return {boolean} Whether an element was removed. + */ +goog.object.remove = function(obj, key) { + var rv; + if ((rv = key in obj)) { + delete obj[key]; + } + return rv; +}; + + +/** + * Adds a key-value pair to the object. Throws an exception if the key is + * already in use. Use set if you want to change an existing pair. + * + * @param {Object.<K,V>} obj The object to which to add the key-value pair. + * @param {string} key The key to add. + * @param {V} val The value to add. + * @template K,V + */ +goog.object.add = function(obj, key, val) { + if (key in obj) { + throw Error('The object already contains the key "' + key + '"'); + } + goog.object.set(obj, key, val); +}; + + +/** + * Returns the value for the given key. + * + * @param {Object.<K,V>} obj The object from which to get the value. + * @param {string} key The key for which to get the value. + * @param {R=} opt_val The value to return if no item is found for the given + * key (default is undefined). + * @return {V|R|undefined} The value for the given key. + * @template K,V,R + */ +goog.object.get = function(obj, key, opt_val) { + if (key in obj) { + return obj[key]; + } + return opt_val; +}; + + +/** + * Adds a key-value pair to the object/map/hash. + * + * @param {Object.<K,V>} obj The object to which to add the key-value pair. + * @param {string} key The key to add. + * @param {V} value The value to add. + * @template K,V + */ +goog.object.set = function(obj, key, value) { + obj[key] = value; +}; + + +/** + * Adds a key-value pair to the object/map/hash if it doesn't exist yet. + * + * @param {Object.<K,V>} obj The object to which to add the key-value pair. + * @param {string} key The key to add. + * @param {V} value The value to add if the key wasn't present. + * @return {V} The value of the entry at the end of the function. + * @template K,V + */ +goog.object.setIfUndefined = function(obj, key, value) { + return key in obj ? obj[key] : (obj[key] = value); +}; + + +/** + * Does a flat clone of the object. + * + * @param {Object.<K,V>} obj Object to clone. + * @return {!Object.<K,V>} Clone of the input object. + * @template K,V + */ +goog.object.clone = function(obj) { + // We cannot use the prototype trick because a lot of methods depend on where + // the actual key is set. + + var res = {}; + for (var key in obj) { + res[key] = obj[key]; + } + return res; + // We could also use goog.mixin but I wanted this to be independent from that. +}; + + +/** + * Clones a value. The input may be an Object, Array, or basic type. Objects and + * arrays will be cloned recursively. + * + * WARNINGS: + * <code>goog.object.unsafeClone</code> does not detect reference loops. Objects + * that refer to themselves will cause infinite recursion. + * + * <code>goog.object.unsafeClone</code> is unaware of unique identifiers, and + * copies UIDs created by <code>getUid</code> into cloned results. + * + * @param {*} obj The value to clone. + * @return {*} A clone of the input value. + */ +goog.object.unsafeClone = function(obj) { + var type = goog.typeOf(obj); + if (type == 'object' || type == 'array') { + if (obj.clone) { + return obj.clone(); + } + var clone = type == 'array' ? [] : {}; + for (var key in obj) { + clone[key] = goog.object.unsafeClone(obj[key]); + } + return clone; + } + + return obj; +}; + + +/** + * Returns a new object in which all the keys and values are interchanged + * (keys become values and values become keys). If multiple keys map to the + * same value, the chosen transposed value is implementation-dependent. + * + * @param {Object} obj The object to transpose. + * @return {!Object} The transposed object. + */ +goog.object.transpose = function(obj) { + var transposed = {}; + for (var key in obj) { + transposed[obj[key]] = key; + } + return transposed; +}; + + +/** + * The names of the fields that are defined on Object.prototype. + * @type {Array.<string>} + * @private + */ +goog.object.PROTOTYPE_FIELDS_ = [ + 'constructor', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'toLocaleString', + 'toString', + 'valueOf' +]; + + +/** + * Extends an object with another object. + * This operates 'in-place'; it does not create a new Object. + * + * Example: + * var o = {}; + * goog.object.extend(o, {a: 0, b: 1}); + * o; // {a: 0, b: 1} + * goog.object.extend(o, {b: 2, c: 3}); + * o; // {a: 0, b: 2, c: 3} + * + * @param {Object} target The object to modify. Existing properties will be + * overwritten if they are also present in one of the objects in + * {@code var_args}. + * @param {...Object} var_args The objects from which values will be copied. + */ +goog.object.extend = function(target, var_args) { + var key, source; + for (var i = 1; i < arguments.length; i++) { + source = arguments[i]; + for (key in source) { + target[key] = source[key]; + } + + // For IE the for-in-loop does not contain any properties that are not + // enumerable on the prototype object (for example isPrototypeOf from + // Object.prototype) and it will also not include 'replace' on objects that + // extend String and change 'replace' (not that it is common for anyone to + // extend anything except Object). + + for (var j = 0; j < goog.object.PROTOTYPE_FIELDS_.length; j++) { + key = goog.object.PROTOTYPE_FIELDS_[j]; + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } +}; + + +/** + * Creates a new object built from the key-value pairs provided as arguments. + * @param {...*} var_args If only one argument is provided and it is an array + * then this is used as the arguments, otherwise even arguments are used as + * the property names and odd arguments are used as the property values. + * @return {!Object} The new object. + * @throws {Error} If there are uneven number of arguments or there is only one + * non array argument. + */ +goog.object.create = function(var_args) { + var argLength = arguments.length; + if (argLength == 1 && goog.isArray(arguments[0])) { + return goog.object.create.apply(null, arguments[0]); + } + + if (argLength % 2) { + throw Error('Uneven number of arguments'); + } + + var rv = {}; + for (var i = 0; i < argLength; i += 2) { + rv[arguments[i]] = arguments[i + 1]; + } + return rv; +}; + + +/** + * Creates a new object where the property names come from the arguments but + * the value is always set to true + * @param {...*} var_args If only one argument is provided and it is an array + * then this is used as the arguments, otherwise the arguments are used + * as the property names. + * @return {!Object} The new object. + */ +goog.object.createSet = function(var_args) { + var argLength = arguments.length; + if (argLength == 1 && goog.isArray(arguments[0])) { + return goog.object.createSet.apply(null, arguments[0]); + } + + var rv = {}; + for (var i = 0; i < argLength; i++) { + rv[arguments[i]] = true; + } + return rv; +}; + + +/** + * Creates an immutable view of the underlying object, if the browser + * supports immutable objects. + * + * In default mode, writes to this view will fail silently. In strict mode, + * they will throw an error. + * + * @param {!Object.<K,V>} obj An object. + * @return {!Object.<K,V>} An immutable view of that object, or the + * original object if this browser does not support immutables. + * @template K,V + */ +goog.object.createImmutableView = function(obj) { + var result = obj; + if (Object.isFrozen && !Object.isFrozen(obj)) { + result = Object.create(obj); + Object.freeze(result); + } + return result; +}; + + +/** + * @param {!Object} obj An object. + * @return {boolean} Whether this is an immutable view of the object. + */ +goog.object.isImmutableView = function(obj) { + return !!Object.isFrozen && Object.isFrozen(obj); +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/positioning/positioning.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/positioning/positioning.js new file mode 100644 index 0000000..0d5c40c --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/positioning/positioning.js @@ -0,0 +1,557 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Common positioning code. + * + */ + +goog.provide('goog.positioning'); +goog.provide('goog.positioning.Corner'); +goog.provide('goog.positioning.CornerBit'); +goog.provide('goog.positioning.Overflow'); +goog.provide('goog.positioning.OverflowStatus'); + +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.math.Box'); +goog.require('goog.math.Coordinate'); +goog.require('goog.math.Size'); +goog.require('goog.style'); +goog.require('goog.style.bidi'); + + +/** + * Enum for representing an element corner for positioning the popup. + * + * The START constants map to LEFT if element directionality is left + * to right and RIGHT if the directionality is right to left. + * Likewise END maps to RIGHT or LEFT depending on the directionality. + * + * @enum {number} + */ +goog.positioning.Corner = { + TOP_LEFT: 0, + TOP_RIGHT: 2, + BOTTOM_LEFT: 1, + BOTTOM_RIGHT: 3, + TOP_START: 4, + TOP_END: 6, + BOTTOM_START: 5, + BOTTOM_END: 7 +}; + + +/** + * Enum for bits in the {@see goog.positioning.Corner) bitmap. + * + * @enum {number} + */ +goog.positioning.CornerBit = { + BOTTOM: 1, + RIGHT: 2, + FLIP_RTL: 4 +}; + + +/** + * Enum for representing position handling in cases where the element would be + * positioned outside the viewport. + * + * @enum {number} + */ +goog.positioning.Overflow = { + /** Ignore overflow */ + IGNORE: 0, + + /** Try to fit horizontally in the viewport at all costs. */ + ADJUST_X: 1, + + /** If the element can't fit horizontally, report positioning failure. */ + FAIL_X: 2, + + /** Try to fit vertically in the viewport at all costs. */ + ADJUST_Y: 4, + + /** If the element can't fit vertically, report positioning failure. */ + FAIL_Y: 8, + + /** Resize the element's width to fit in the viewport. */ + RESIZE_WIDTH: 16, + + /** Resize the element's height to fit in the viewport. */ + RESIZE_HEIGHT: 32, + + /** + * If the anchor goes off-screen in the x-direction, position the movable + * element off-screen. Otherwise, try to fit horizontally in the viewport. + */ + ADJUST_X_EXCEPT_OFFSCREEN: 64 | 1, + + /** + * If the anchor goes off-screen in the y-direction, position the movable + * element off-screen. Otherwise, try to fit vertically in the viewport. + */ + ADJUST_Y_EXCEPT_OFFSCREEN: 128 | 4 +}; + + +/** + * Enum for representing the outcome of a positioning call. + * + * @enum {number} + */ +goog.positioning.OverflowStatus = { + NONE: 0, + ADJUSTED_X: 1, + ADJUSTED_Y: 2, + WIDTH_ADJUSTED: 4, + HEIGHT_ADJUSTED: 8, + FAILED_LEFT: 16, + FAILED_RIGHT: 32, + FAILED_TOP: 64, + FAILED_BOTTOM: 128, + FAILED_OUTSIDE_VIEWPORT: 256 +}; + + +/** + * Shorthand to check if a status code contains any fail code. + * @type {number} + */ +goog.positioning.OverflowStatus.FAILED = + goog.positioning.OverflowStatus.FAILED_LEFT | + goog.positioning.OverflowStatus.FAILED_RIGHT | + goog.positioning.OverflowStatus.FAILED_TOP | + goog.positioning.OverflowStatus.FAILED_BOTTOM | + goog.positioning.OverflowStatus.FAILED_OUTSIDE_VIEWPORT; + + +/** + * Shorthand to check if horizontal positioning failed. + * @type {number} + */ +goog.positioning.OverflowStatus.FAILED_HORIZONTAL = + goog.positioning.OverflowStatus.FAILED_LEFT | + goog.positioning.OverflowStatus.FAILED_RIGHT; + + +/** + * Shorthand to check if vertical positioning failed. + * @type {number} + */ +goog.positioning.OverflowStatus.FAILED_VERTICAL = + goog.positioning.OverflowStatus.FAILED_TOP | + goog.positioning.OverflowStatus.FAILED_BOTTOM; + + +/** + * Positions a movable element relative to an anchor element. The caller + * specifies the corners that should touch. This functions then moves the + * movable element accordingly. + * + * @param {Element} anchorElement The element that is the anchor for where + * the movable element should position itself. + * @param {goog.positioning.Corner} anchorElementCorner The corner of the + * anchorElement for positioning the movable element. + * @param {Element} movableElement The element to move. + * @param {goog.positioning.Corner} movableElementCorner The corner of the + * movableElement that that should be positioned adjacent to the anchor + * element. + * @param {goog.math.Coordinate=} opt_offset An offset specified in pixels. + * After the normal positioning algorithm is applied, the offset is then + * applied. Positive coordinates move the popup closer to the center of the + * anchor element. Negative coordinates move the popup away from the center + * of the anchor element. + * @param {goog.math.Box=} opt_margin A margin specified in pixels. + * After the normal positioning algorithm is applied and any offset, the + * margin is then applied. Positive coordinates move the popup away from the + * spot it was positioned towards its center. Negative coordinates move it + * towards the spot it was positioned away from its center. + * @param {?number=} opt_overflow Overflow handling mode. Defaults to IGNORE if + * not specified. Bitmap, {@see goog.positioning.Overflow}. + * @param {goog.math.Size=} opt_preferredSize The preferred size of the + * movableElement. + * @param {goog.math.Box=} opt_viewport Box object describing the dimensions of + * the viewport. The viewport is specified relative to offsetParent of + * {@code movableElement}. In other words, the viewport can be thought of as + * describing a "position: absolute" element contained in the offsetParent. + * It defaults to visible area of nearest scrollable ancestor of + * {@code movableElement} (see {@code goog.style.getVisibleRectForElement}). + * @return {goog.positioning.OverflowStatus} Status bitmap, + * {@see goog.positioning.OverflowStatus}. + */ +goog.positioning.positionAtAnchor = function(anchorElement, + anchorElementCorner, + movableElement, + movableElementCorner, + opt_offset, + opt_margin, + opt_overflow, + opt_preferredSize, + opt_viewport) { + + goog.asserts.assert(movableElement); + var movableParentTopLeft = + goog.positioning.getOffsetParentPageOffset(movableElement); + + // Get the visible part of the anchor element. anchorRect is + // relative to anchorElement's page. + var anchorRect = goog.positioning.getVisiblePart_(anchorElement); + + // Translate anchorRect to be relative to movableElement's page. + goog.style.translateRectForAnotherFrame( + anchorRect, + goog.dom.getDomHelper(anchorElement), + goog.dom.getDomHelper(movableElement)); + + // Offset based on which corner of the element we want to position against. + var corner = goog.positioning.getEffectiveCorner(anchorElement, + anchorElementCorner); + // absolutePos is a candidate position relative to the + // movableElement's window. + var absolutePos = new goog.math.Coordinate( + corner & goog.positioning.CornerBit.RIGHT ? + anchorRect.left + anchorRect.width : anchorRect.left, + corner & goog.positioning.CornerBit.BOTTOM ? + anchorRect.top + anchorRect.height : anchorRect.top); + + // Translate absolutePos to be relative to the offsetParent. + absolutePos = + goog.math.Coordinate.difference(absolutePos, movableParentTopLeft); + + // Apply offset, if specified + if (opt_offset) { + absolutePos.x += (corner & goog.positioning.CornerBit.RIGHT ? -1 : 1) * + opt_offset.x; + absolutePos.y += (corner & goog.positioning.CornerBit.BOTTOM ? -1 : 1) * + opt_offset.y; + } + + // Determine dimension of viewport. + var viewport; + if (opt_overflow) { + if (opt_viewport) { + viewport = opt_viewport; + } else { + viewport = goog.style.getVisibleRectForElement(movableElement); + if (viewport) { + viewport.top -= movableParentTopLeft.y; + viewport.right -= movableParentTopLeft.x; + viewport.bottom -= movableParentTopLeft.y; + viewport.left -= movableParentTopLeft.x; + } + } + } + + return goog.positioning.positionAtCoordinate(absolutePos, + movableElement, + movableElementCorner, + opt_margin, + viewport, + opt_overflow, + opt_preferredSize); +}; + + +/** + * Calculates the page offset of the given element's + * offsetParent. This value can be used to translate any x- and + * y-offset relative to the page to an offset relative to the + * offsetParent, which can then be used directly with as position + * coordinate for {@code positionWithCoordinate}. + * @param {!Element} movableElement The element to calculate. + * @return {!goog.math.Coordinate} The page offset, may be (0, 0). + */ +goog.positioning.getOffsetParentPageOffset = function(movableElement) { + // Ignore offset for the BODY element unless its position is non-static. + // For cases where the offset parent is HTML rather than the BODY (such as in + // IE strict mode) there's no need to get the position of the BODY as it + // doesn't affect the page offset. + var movableParentTopLeft; + var parent = movableElement.offsetParent; + if (parent) { + var isBody = parent.tagName == goog.dom.TagName.HTML || + parent.tagName == goog.dom.TagName.BODY; + if (!isBody || + goog.style.getComputedPosition(parent) != 'static') { + // Get the top-left corner of the parent, in page coordinates. + movableParentTopLeft = goog.style.getPageOffset(parent); + + if (!isBody) { + movableParentTopLeft = goog.math.Coordinate.difference( + movableParentTopLeft, + new goog.math.Coordinate(goog.style.bidi.getScrollLeft(parent), + parent.scrollTop)); + } + } + } + + return movableParentTopLeft || new goog.math.Coordinate(); +}; + + +/** + * Returns intersection of the specified element and + * goog.style.getVisibleRectForElement for it. + * + * @param {Element} el The target element. + * @return {!goog.math.Rect} Intersection of getVisibleRectForElement + * and the current bounding rectangle of the element. If the + * intersection is empty, returns the bounding rectangle. + * @private + */ +goog.positioning.getVisiblePart_ = function(el) { + var rect = goog.style.getBounds(el); + var visibleBox = goog.style.getVisibleRectForElement(el); + if (visibleBox) { + rect.intersection(goog.math.Rect.createFromBox(visibleBox)); + } + return rect; +}; + + +/** + * Positions the specified corner of the movable element at the + * specified coordinate. + * + * @param {goog.math.Coordinate} absolutePos The coordinate to position the + * element at. + * @param {Element} movableElement The element to be positioned. + * @param {goog.positioning.Corner} movableElementCorner The corner of the + * movableElement that that should be positioned. + * @param {goog.math.Box=} opt_margin A margin specified in pixels. + * After the normal positioning algorithm is applied and any offset, the + * margin is then applied. Positive coordinates move the popup away from the + * spot it was positioned towards its center. Negative coordinates move it + * towards the spot it was positioned away from its center. + * @param {goog.math.Box=} opt_viewport Box object describing the dimensions of + * the viewport. Required if opt_overflow is specified. + * @param {?number=} opt_overflow Overflow handling mode. Defaults to IGNORE if + * not specified, {@see goog.positioning.Overflow}. + * @param {goog.math.Size=} opt_preferredSize The preferred size of the + * movableElement. Defaults to the current size. + * @return {goog.positioning.OverflowStatus} Status bitmap. + */ +goog.positioning.positionAtCoordinate = function(absolutePos, + movableElement, + movableElementCorner, + opt_margin, + opt_viewport, + opt_overflow, + opt_preferredSize) { + absolutePos = absolutePos.clone(); + var status = goog.positioning.OverflowStatus.NONE; + + // Offset based on attached corner and desired margin. + var corner = goog.positioning.getEffectiveCorner(movableElement, + movableElementCorner); + var elementSize = goog.style.getSize(movableElement); + var size = opt_preferredSize ? opt_preferredSize.clone() : + elementSize.clone(); + + if (opt_margin || corner != goog.positioning.Corner.TOP_LEFT) { + if (corner & goog.positioning.CornerBit.RIGHT) { + absolutePos.x -= size.width + (opt_margin ? opt_margin.right : 0); + } else if (opt_margin) { + absolutePos.x += opt_margin.left; + } + if (corner & goog.positioning.CornerBit.BOTTOM) { + absolutePos.y -= size.height + (opt_margin ? opt_margin.bottom : 0); + } else if (opt_margin) { + absolutePos.y += opt_margin.top; + } + } + + // Adjust position to fit inside viewport. + if (opt_overflow) { + status = opt_viewport ? + goog.positioning.adjustForViewport_( + absolutePos, size, opt_viewport, opt_overflow) : + goog.positioning.OverflowStatus.FAILED_OUTSIDE_VIEWPORT; + if (status & goog.positioning.OverflowStatus.FAILED) { + return status; + } + } + + goog.style.setPosition(movableElement, absolutePos); + if (!goog.math.Size.equals(elementSize, size)) { + goog.style.setBorderBoxSize(movableElement, size); + } + + return status; +}; + + +/** + * Adjusts the position and/or size of an element, identified by its position + * and size, to fit inside the viewport. If the position or size of the element + * is adjusted the pos or size objects, respectively, are modified. + * + * @param {goog.math.Coordinate} pos Position of element, updated if the + * position is adjusted. + * @param {goog.math.Size} size Size of element, updated if the size is + * adjusted. + * @param {goog.math.Box} viewport Bounding box describing the viewport. + * @param {number} overflow Overflow handling mode, + * {@see goog.positioning.Overflow}. + * @return {goog.positioning.OverflowStatus} Status bitmap, + * {@see goog.positioning.OverflowStatus}. + * @private + */ +goog.positioning.adjustForViewport_ = function(pos, size, viewport, overflow) { + var status = goog.positioning.OverflowStatus.NONE; + + var ADJUST_X_EXCEPT_OFFSCREEN = + goog.positioning.Overflow.ADJUST_X_EXCEPT_OFFSCREEN; + var ADJUST_Y_EXCEPT_OFFSCREEN = + goog.positioning.Overflow.ADJUST_Y_EXCEPT_OFFSCREEN; + if ((overflow & ADJUST_X_EXCEPT_OFFSCREEN) == ADJUST_X_EXCEPT_OFFSCREEN && + (pos.x < viewport.left || pos.x >= viewport.right)) { + overflow &= ~goog.positioning.Overflow.ADJUST_X; + } + if ((overflow & ADJUST_Y_EXCEPT_OFFSCREEN) == ADJUST_Y_EXCEPT_OFFSCREEN && + (pos.y < viewport.top || pos.y >= viewport.bottom)) { + overflow &= ~goog.positioning.Overflow.ADJUST_Y; + } + + // Left edge outside viewport, try to move it. + if (pos.x < viewport.left && overflow & goog.positioning.Overflow.ADJUST_X) { + pos.x = viewport.left; + status |= goog.positioning.OverflowStatus.ADJUSTED_X; + } + + // Left edge inside and right edge outside viewport, try to resize it. + if (pos.x < viewport.left && + pos.x + size.width > viewport.right && + overflow & goog.positioning.Overflow.RESIZE_WIDTH) { + size.width = Math.max( + size.width - ((pos.x + size.width) - viewport.right), 0); + status |= goog.positioning.OverflowStatus.WIDTH_ADJUSTED; + } + + // Right edge outside viewport, try to move it. + if (pos.x + size.width > viewport.right && + overflow & goog.positioning.Overflow.ADJUST_X) { + pos.x = Math.max(viewport.right - size.width, viewport.left); + status |= goog.positioning.OverflowStatus.ADJUSTED_X; + } + + // Left or right edge still outside viewport, fail if the FAIL_X option was + // specified, ignore it otherwise. + if (overflow & goog.positioning.Overflow.FAIL_X) { + status |= (pos.x < viewport.left ? + goog.positioning.OverflowStatus.FAILED_LEFT : 0) | + (pos.x + size.width > viewport.right ? + goog.positioning.OverflowStatus.FAILED_RIGHT : 0); + } + + // Top edge outside viewport, try to move it. + if (pos.y < viewport.top && overflow & goog.positioning.Overflow.ADJUST_Y) { + pos.y = viewport.top; + status |= goog.positioning.OverflowStatus.ADJUSTED_Y; + } + + // Bottom edge inside and top edge outside viewport, try to resize it. + if (pos.y <= viewport.top && + pos.y + size.height < viewport.bottom && + overflow & goog.positioning.Overflow.RESIZE_HEIGHT) { + size.height = Math.max(size.height - (viewport.top - pos.y), 0); + pos.y = viewport.top; + status |= goog.positioning.OverflowStatus.HEIGHT_ADJUSTED; + } + + // Top edge inside and bottom edge outside viewport, try to resize it. + if (pos.y >= viewport.top && + pos.y + size.height > viewport.bottom && + overflow & goog.positioning.Overflow.RESIZE_HEIGHT) { + size.height = Math.max( + size.height - ((pos.y + size.height) - viewport.bottom), 0); + status |= goog.positioning.OverflowStatus.HEIGHT_ADJUSTED; + } + + // Bottom edge outside viewport, try to move it. + if (pos.y + size.height > viewport.bottom && + overflow & goog.positioning.Overflow.ADJUST_Y) { + pos.y = Math.max(viewport.bottom - size.height, viewport.top); + status |= goog.positioning.OverflowStatus.ADJUSTED_Y; + } + + // Top or bottom edge still outside viewport, fail if the FAIL_Y option was + // specified, ignore it otherwise. + if (overflow & goog.positioning.Overflow.FAIL_Y) { + status |= (pos.y < viewport.top ? + goog.positioning.OverflowStatus.FAILED_TOP : 0) | + (pos.y + size.height > viewport.bottom ? + goog.positioning.OverflowStatus.FAILED_BOTTOM : 0); + } + + return status; +}; + + +/** + * Returns an absolute corner (top/bottom left/right) given an absolute + * or relative (top/bottom start/end) corner and the direction of an element. + * Absolute corners remain unchanged. + * @param {Element} element DOM element to test for RTL direction. + * @param {goog.positioning.Corner} corner The popup corner used for + * positioning. + * @return {goog.positioning.Corner} Effective corner. + */ +goog.positioning.getEffectiveCorner = function(element, corner) { + return /** @type {goog.positioning.Corner} */ ( + (corner & goog.positioning.CornerBit.FLIP_RTL && + goog.style.isRightToLeft(element) ? + corner ^ goog.positioning.CornerBit.RIGHT : + corner + ) & ~goog.positioning.CornerBit.FLIP_RTL); +}; + + +/** + * Returns the corner opposite the given one horizontally. + * @param {goog.positioning.Corner} corner The popup corner used to flip. + * @return {goog.positioning.Corner} The opposite corner horizontally. + */ +goog.positioning.flipCornerHorizontal = function(corner) { + return /** @type {goog.positioning.Corner} */ (corner ^ + goog.positioning.CornerBit.RIGHT); +}; + + +/** + * Returns the corner opposite the given one vertically. + * @param {goog.positioning.Corner} corner The popup corner used to flip. + * @return {goog.positioning.Corner} The opposite corner vertically. + */ +goog.positioning.flipCornerVertical = function(corner) { + return /** @type {goog.positioning.Corner} */ (corner ^ + goog.positioning.CornerBit.BOTTOM); +}; + + +/** + * Returns the corner opposite the given one horizontally and vertically. + * @param {goog.positioning.Corner} corner The popup corner used to flip. + * @return {goog.positioning.Corner} The opposite corner horizontally and + * vertically. + */ +goog.positioning.flipCorner = function(corner) { + return /** @type {goog.positioning.Corner} */ (corner ^ + goog.positioning.CornerBit.BOTTOM ^ + goog.positioning.CornerBit.RIGHT); +}; + diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/promise.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/promise.js new file mode 100644 index 0000000..1c9ef5c --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/promise.js @@ -0,0 +1,985 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +goog.provide('goog.Promise'); + +goog.require('goog.Thenable'); +goog.require('goog.asserts'); +goog.require('goog.async.run'); +goog.require('goog.async.throwException'); +goog.require('goog.debug.Error'); +goog.require('goog.promise.Resolver'); + + + +/** + * Promises provide a result that may be resolved asynchronously. A Promise may + * be resolved by being fulfilled or rejected with a value, which will be known + * as the fulfillment value or the rejection reason. Whether fulfilled or + * rejected, the Promise result is immutable once it is set. + * + * Promises may represent results of any type, including undefined. Rejection + * reasons are typically Errors, but may also be of any type. Closure Promises + * allow for optional type annotations that enforce that fulfillment values are + * of the appropriate types at compile time. + * + * The result of a Promise is accessible by calling {@code then} and registering + * {@code onFulfilled} and {@code onRejected} callbacks. Once the Promise + * resolves, the relevant callbacks are invoked with the fulfillment value or + * rejection reason as argument. Callbacks are always invoked in the order they + * were registered, even when additional {@code then} calls are made from inside + * another callback. A callback is always run asynchronously sometime after the + * scope containing the registering {@code then} invocation has returned. + * + * If a Promise is resolved with another Promise, the first Promise will block + * until the second is resolved, and then assumes the same result as the second + * Promise. This allows Promises to depend on the results of other Promises, + * linking together multiple asynchronous operations. + * + * This implementation is compatible with the Promises/A+ specification and + * passes that specification's conformance test suite. A Closure Promise may be + * resolved with a Promise instance (or sufficiently compatible Promise-like + * object) created by other Promise implementations. From the specification, + * Promise-like objects are known as "Thenables". + * + * @see http://promisesaplus.com/ + * + * @param {function( + * this:RESOLVER_CONTEXT, + * function((TYPE|IThenable.<TYPE>|Thenable)=), + * function(*)): void} resolver + * Initialization function that is invoked immediately with {@code resolve} + * and {@code reject} functions as arguments. The Promise is resolved or + * rejected with the first argument passed to either function. + * @param {RESOLVER_CONTEXT=} opt_context An optional context for executing the + * resolver function. If unspecified, the resolver function will be executed + * in the default scope. + * @constructor + * @struct + * @final + * @implements {goog.Thenable.<TYPE>} + * @template TYPE,RESOLVER_CONTEXT + */ +goog.Promise = function(resolver, opt_context) { + /** + * The internal state of this Promise. Either PENDING, FULFILLED, REJECTED, or + * BLOCKED. + * @private {goog.Promise.State_} + */ + this.state_ = goog.Promise.State_.PENDING; + + /** + * The resolved result of the Promise. Immutable once set with either a + * fulfillment value or rejection reason. + * @private {*} + */ + this.result_ = undefined; + + /** + * For Promises created by calling {@code then()}, the originating parent. + * @private {goog.Promise} + */ + this.parent_ = null; + + /** + * The list of {@code onFulfilled} and {@code onRejected} callbacks added to + * this Promise by calls to {@code then()}. + * @private {Array.<goog.Promise.CallbackEntry_>} + */ + this.callbackEntries_ = null; + + /** + * Whether the Promise is in the queue of Promises to execute. + * @private {boolean} + */ + this.executing_ = false; + + if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) { + /** + * A timeout ID used when the {@code UNHANDLED_REJECTION_DELAY} is greater + * than 0 milliseconds. The ID is set when the Promise is rejected, and + * cleared only if an {@code onRejected} callback is invoked for the + * Promise (or one of its descendants) before the delay is exceeded. + * + * If the rejection is not handled before the timeout completes, the + * rejection reason is passed to the unhandled rejection handler. + * @private {number} + */ + this.unhandledRejectionId_ = 0; + } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) { + /** + * When the {@code UNHANDLED_REJECTION_DELAY} is set to 0 milliseconds, a + * boolean that is set if the Promise is rejected, and reset to false if an + * {@code onRejected} callback is invoked for the Promise (or one of its + * descendants). If the rejection is not handled before the next timestep, + * the rejection reason is passed to the unhandled rejection handler. + * @private {boolean} + */ + this.hadUnhandledRejection_ = false; + } + + if (goog.Promise.LONG_STACK_TRACES) { + /** + * A list of stack trace frames pointing to the locations where this Promise + * was created or had callbacks added to it. Saved to add additional context + * to stack traces when an exception is thrown. + * @private {!Array.<string>} + */ + this.stack_ = []; + this.addStackTrace_(new Error('created')); + + /** + * Index of the most recently executed stack frame entry. + * @private {number} + */ + this.currentStep_ = 0; + } + + try { + var self = this; + resolver.call( + opt_context, + function(value) { + self.resolve_(goog.Promise.State_.FULFILLED, value); + }, + function(reason) { + self.resolve_(goog.Promise.State_.REJECTED, reason); + }); + } catch (e) { + this.resolve_(goog.Promise.State_.REJECTED, e); + } +}; + + +/** + * @define {boolean} Whether traces of {@code then} calls should be included in + * exceptions thrown + */ +goog.define('goog.Promise.LONG_STACK_TRACES', false); + + +/** + * @define {number} The delay in milliseconds before a rejected Promise's reason + * is passed to the rejection handler. By default, the rejection handler + * rethrows the rejection reason so that it appears in the developer console or + * {@code window.onerror} handler. + * + * Rejections are rethrown as quickly as possible by default. A negative value + * disables rejection handling entirely. + */ +goog.define('goog.Promise.UNHANDLED_REJECTION_DELAY', 0); + + +/** + * The possible internal states for a Promise. These states are not directly + * observable to external callers. + * @enum {number} + * @private + */ +goog.Promise.State_ = { + /** The Promise is waiting for resolution. */ + PENDING: 0, + + /** The Promise is blocked waiting for the result of another Thenable. */ + BLOCKED: 1, + + /** The Promise has been resolved with a fulfillment value. */ + FULFILLED: 2, + + /** The Promise has been resolved with a rejection reason. */ + REJECTED: 3 +}; + + +/** + * Typedef for entries in the callback chain. Each call to {@code then}, + * {@code thenCatch}, or {@code thenAlways} creates an entry containing the + * functions that may be invoked once the Promise is resolved. + * + * @typedef {{ + * child: goog.Promise, + * onFulfilled: function(*), + * onRejected: function(*) + * }} + * @private + */ +goog.Promise.CallbackEntry_; + + +/** + * @param {(TYPE|goog.Thenable.<TYPE>|Thenable)=} opt_value + * @return {!goog.Promise.<TYPE>} A new Promise that is immediately resolved + * with the given value. + * @template TYPE + */ +goog.Promise.resolve = function(opt_value) { + return new goog.Promise(function(resolve, reject) { + resolve(opt_value); + }); +}; + + +/** + * @param {*=} opt_reason + * @return {!goog.Promise} A new Promise that is immediately rejected with the + * given reason. + */ +goog.Promise.reject = function(opt_reason) { + return new goog.Promise(function(resolve, reject) { + reject(opt_reason); + }); +}; + + +/** + * @param {!Array.<!(goog.Thenable.<TYPE>|Thenable)>} promises + * @return {!goog.Promise.<TYPE>} A Promise that receives the result of the + * first Promise (or Promise-like) input to complete. + * @template TYPE + */ +goog.Promise.race = function(promises) { + return new goog.Promise(function(resolve, reject) { + if (!promises.length) { + resolve(undefined); + } + for (var i = 0, promise; promise = promises[i]; i++) { + promise.then(resolve, reject); + } + }); +}; + + +/** + * @param {!Array.<!(goog.Thenable.<TYPE>|Thenable)>} promises + * @return {!goog.Promise.<!Array.<TYPE>>} A Promise that receives a list of + * every fulfilled value once every input Promise (or Promise-like) is + * successfully fulfilled, or is rejected by the first rejection result. + * @template TYPE + */ +goog.Promise.all = function(promises) { + return new goog.Promise(function(resolve, reject) { + var toFulfill = promises.length; + var values = []; + + if (!toFulfill) { + resolve(values); + return; + } + + var onFulfill = function(index, value) { + toFulfill--; + values[index] = value; + if (toFulfill == 0) { + resolve(values); + } + }; + + var onReject = function(reason) { + reject(reason); + }; + + for (var i = 0, promise; promise = promises[i]; i++) { + promise.then(goog.partial(onFulfill, i), onReject); + } + }); +}; + + +/** + * @param {!Array.<!(goog.Thenable.<TYPE>|Thenable)>} promises + * @return {!goog.Promise.<TYPE>} A Promise that receives the value of the first + * input to be fulfilled, or is rejected with a list of every rejection + * reason if all inputs are rejected. + * @template TYPE + */ +goog.Promise.firstFulfilled = function(promises) { + return new goog.Promise(function(resolve, reject) { + var toReject = promises.length; + var reasons = []; + + if (!toReject) { + resolve(undefined); + return; + } + + var onFulfill = function(value) { + resolve(value); + }; + + var onReject = function(index, reason) { + toReject--; + reasons[index] = reason; + if (toReject == 0) { + reject(reasons); + } + }; + + for (var i = 0, promise; promise = promises[i]; i++) { + promise.then(onFulfill, goog.partial(onReject, i)); + } + }); +}; + + +/** + * @return {!goog.promise.Resolver.<TYPE>} Resolver wrapping the promise and its + * resolve / reject functions. Resolving or rejecting the resolver + * resolves or rejects the promise. + * @template TYPE + */ +goog.Promise.withResolver = function() { + var resolve, reject; + var promise = new goog.Promise(function(rs, rj) { + resolve = rs; + reject = rj; + }); + return new goog.Promise.Resolver_(promise, resolve, reject); +}; + + +/** + * Adds callbacks that will operate on the result of the Promise, returning a + * new child Promise. + * + * If the Promise is fulfilled, the {@code onFulfilled} callback will be invoked + * with the fulfillment value as argument, and the child Promise will be + * fulfilled with the return value of the callback. If the callback throws an + * exception, the child Promise will be rejected with the thrown value instead. + * + * If the Promise is rejected, the {@code onRejected} callback will be invoked + * with the rejection reason as argument, and the child Promise will be resolved + * with the return value or rejected with the thrown value of the callback. + * + * @override + */ +goog.Promise.prototype.then = function( + opt_onFulfilled, opt_onRejected, opt_context) { + + if (opt_onFulfilled != null) { + goog.asserts.assertFunction(opt_onFulfilled, + 'opt_onFulfilled should be a function.'); + } + if (opt_onRejected != null) { + goog.asserts.assertFunction(opt_onRejected, + 'opt_onRejected should be a function. Did you pass opt_context ' + + 'as the second argument instead of the third?'); + } + + if (goog.Promise.LONG_STACK_TRACES) { + this.addStackTrace_(new Error('then')); + } + + return this.addChildPromise_( + goog.isFunction(opt_onFulfilled) ? opt_onFulfilled : null, + goog.isFunction(opt_onRejected) ? opt_onRejected : null, + opt_context); +}; +goog.Thenable.addImplementation(goog.Promise); + + +/** + * Adds a callback that will be invoked whether the Promise is fulfilled or + * rejected. The callback receives no argument, and no new child Promise is + * created. This is useful for ensuring that cleanup takes place after certain + * asynchronous operations. Callbacks added with {@code thenAlways} will be + * executed in the same order with other calls to {@code then}, + * {@code thenAlways}, or {@code thenCatch}. + * + * Since it does not produce a new child Promise, cancellation propagation is + * not prevented by adding callbacks with {@code thenAlways}. A Promise that has + * a cleanup handler added with {@code thenAlways} will be canceled if all of + * its children created by {@code then} (or {@code thenCatch}) are canceled. + * + * @param {function(this:THIS): void} onResolved A function that will be invoked + * when the Promise is resolved. + * @param {THIS=} opt_context An optional context object that will be the + * execution context for the callbacks. By default, functions are executed + * in the global scope. + * @return {!goog.Promise.<TYPE>} This Promise, for chaining additional calls. + * @template THIS + */ +goog.Promise.prototype.thenAlways = function(onResolved, opt_context) { + if (goog.Promise.LONG_STACK_TRACES) { + this.addStackTrace_(new Error('thenAlways')); + } + + var callback = function() { + try { + // Ensure that no arguments are passed to onResolved. + onResolved.call(opt_context); + } catch (err) { + goog.Promise.handleRejection_.call(null, err); + } + }; + + this.addCallbackEntry_({ + child: null, + onRejected: callback, + onFulfilled: callback + }); + return this; +}; + + +/** + * Adds a callback that will be invoked only if the Promise is rejected. This + * is equivalent to {@code then(null, onRejected)}. + * + * @param {!function(this:THIS, *): *} onRejected A function that will be + * invoked with the rejection reason if the Promise is rejected. + * @param {THIS=} opt_context An optional context object that will be the + * execution context for the callbacks. By default, functions are executed + * in the global scope. + * @return {!goog.Promise} A new Promise that will receive the result of the + * callback. + * @template THIS + */ +goog.Promise.prototype.thenCatch = function(onRejected, opt_context) { + if (goog.Promise.LONG_STACK_TRACES) { + this.addStackTrace_(new Error('thenCatch')); + } + return this.addChildPromise_(null, onRejected, opt_context); +}; + + +/** + * Cancels the Promise if it is still pending by rejecting it with a cancel + * Error. No action is performed if the Promise is already resolved. + * + * All child Promises of the canceled Promise will be rejected with the same + * cancel error, as with normal Promise rejection. If the Promise to be canceled + * is the only child of a pending Promise, the parent Promise will also be + * canceled. Cancellation may propagate upward through multiple generations. + * + * @param {string=} opt_message An optional debugging message for describing the + * cancellation reason. + */ +goog.Promise.prototype.cancel = function(opt_message) { + if (this.state_ == goog.Promise.State_.PENDING) { + goog.async.run(function() { + var err = new goog.Promise.CancellationError(opt_message); + this.cancelInternal_(err); + }, this); + } +}; + + +/** + * Cancels this Promise with the given error. + * + * @param {!Error} err The cancellation error. + * @private + */ +goog.Promise.prototype.cancelInternal_ = function(err) { + if (this.state_ == goog.Promise.State_.PENDING) { + if (this.parent_) { + // Cancel the Promise and remove it from the parent's child list. + this.parent_.cancelChild_(this, err); + } else { + this.resolve_(goog.Promise.State_.REJECTED, err); + } + } +}; + + +/** + * Cancels a child Promise from the list of callback entries. If the Promise has + * not already been resolved, reject it with a cancel error. If there are no + * other children in the list of callback entries, propagate the cancellation + * by canceling this Promise as well. + * + * @param {!goog.Promise} childPromise The Promise to cancel. + * @param {!Error} err The cancel error to use for rejecting the Promise. + * @private + */ +goog.Promise.prototype.cancelChild_ = function(childPromise, err) { + if (!this.callbackEntries_) { + return; + } + var childCount = 0; + var childIndex = -1; + + // Find the callback entry for the childPromise, and count whether there are + // additional child Promises. + for (var i = 0, entry; entry = this.callbackEntries_[i]; i++) { + var child = entry.child; + if (child) { + childCount++; + if (child == childPromise) { + childIndex = i; + } + if (childIndex >= 0 && childCount > 1) { + break; + } + } + } + + // If the child Promise was the only child, cancel this Promise as well. + // Otherwise, reject only the child Promise with the cancel error. + if (childIndex >= 0) { + if (this.state_ == goog.Promise.State_.PENDING && childCount == 1) { + this.cancelInternal_(err); + } else { + var callbackEntry = this.callbackEntries_.splice(childIndex, 1)[0]; + this.executeCallback_( + callbackEntry, goog.Promise.State_.REJECTED, err); + } + } +}; + + +/** + * Adds a callback entry to the current Promise, and schedules callback + * execution if the Promise has already been resolved. + * + * @param {goog.Promise.CallbackEntry_} callbackEntry Record containing + * {@code onFulfilled} and {@code onRejected} callbacks to execute after + * the Promise is resolved. + * @private + */ +goog.Promise.prototype.addCallbackEntry_ = function(callbackEntry) { + if ((!this.callbackEntries_ || !this.callbackEntries_.length) && + (this.state_ == goog.Promise.State_.FULFILLED || + this.state_ == goog.Promise.State_.REJECTED)) { + this.scheduleCallbacks_(); + } + if (!this.callbackEntries_) { + this.callbackEntries_ = []; + } + this.callbackEntries_.push(callbackEntry); +}; + + +/** + * Creates a child Promise and adds it to the callback entry list. The result of + * the child Promise is determined by the state of the parent Promise and the + * result of the {@code onFulfilled} or {@code onRejected} callbacks as + * specified in the Promise resolution procedure. + * + * @see http://promisesaplus.com/#the__method + * + * @param {?function(this:THIS, TYPE): + * (RESULT|goog.Promise.<RESULT>|Thenable)} onFulfilled A callback that + * will be invoked if the Promise is fullfilled, or null. + * @param {?function(this:THIS, *): *} onRejected A callback that will be + * invoked if the Promise is rejected, or null. + * @param {THIS=} opt_context An optional execution context for the callbacks. + * in the default calling context. + * @return {!goog.Promise} The child Promise. + * @template RESULT,THIS + * @private + */ +goog.Promise.prototype.addChildPromise_ = function( + onFulfilled, onRejected, opt_context) { + + var callbackEntry = { + child: null, + onFulfilled: null, + onRejected: null + }; + + callbackEntry.child = new goog.Promise(function(resolve, reject) { + // Invoke onFulfilled, or resolve with the parent's value if absent. + callbackEntry.onFulfilled = onFulfilled ? function(value) { + try { + var result = onFulfilled.call(opt_context, value); + resolve(result); + } catch (err) { + reject(err); + } + } : resolve; + + // Invoke onRejected, or reject with the parent's reason if absent. + callbackEntry.onRejected = onRejected ? function(reason) { + try { + var result = onRejected.call(opt_context, reason); + if (!goog.isDef(result) && + reason instanceof goog.Promise.CancellationError) { + // Propagate cancellation to children if no other result is returned. + reject(reason); + } else { + resolve(result); + } + } catch (err) { + reject(err); + } + } : reject; + }); + + callbackEntry.child.parent_ = this; + this.addCallbackEntry_( + /** @type {goog.Promise.CallbackEntry_} */ (callbackEntry)); + return callbackEntry.child; +}; + + +/** + * Unblocks the Promise and fulfills it with the given value. + * + * @param {TYPE} value + * @private + */ +goog.Promise.prototype.unblockAndFulfill_ = function(value) { + goog.asserts.assert(this.state_ == goog.Promise.State_.BLOCKED); + this.state_ = goog.Promise.State_.PENDING; + this.resolve_(goog.Promise.State_.FULFILLED, value); +}; + + +/** + * Unblocks the Promise and rejects it with the given rejection reason. + * + * @param {*} reason + * @private + */ +goog.Promise.prototype.unblockAndReject_ = function(reason) { + goog.asserts.assert(this.state_ == goog.Promise.State_.BLOCKED); + this.state_ = goog.Promise.State_.PENDING; + this.resolve_(goog.Promise.State_.REJECTED, reason); +}; + + +/** + * Attempts to resolve a Promise with a given resolution state and value. This + * is a no-op if the given Promise has already been resolved. + * + * If the given result is a Thenable (such as another Promise), the Promise will + * be resolved with the same state and result as the Thenable once it is itself + * resolved. + * + * If the given result is not a Thenable, the Promise will be fulfilled or + * rejected with that result based on the given state. + * + * @see http://promisesaplus.com/#the_promise_resolution_procedure + * + * @param {goog.Promise.State_} state + * @param {*} x The result to apply to the Promise. + * @private + */ +goog.Promise.prototype.resolve_ = function(state, x) { + if (this.state_ != goog.Promise.State_.PENDING) { + return; + } + + if (this == x) { + state = goog.Promise.State_.REJECTED; + x = new TypeError('Promise cannot resolve to itself'); + + } else if (goog.Thenable.isImplementedBy(x)) { + x = /** @type {!goog.Thenable} */ (x); + this.state_ = goog.Promise.State_.BLOCKED; + x.then(this.unblockAndFulfill_, this.unblockAndReject_, this); + return; + + } else if (goog.isObject(x)) { + try { + var then = x['then']; + if (goog.isFunction(then)) { + this.tryThen_(x, then); + return; + } + } catch (e) { + state = goog.Promise.State_.REJECTED; + x = e; + } + } + + this.result_ = x; + this.state_ = state; + this.scheduleCallbacks_(); + + if (state == goog.Promise.State_.REJECTED && + !(x instanceof goog.Promise.CancellationError)) { + goog.Promise.addUnhandledRejection_(this, x); + } +}; + + +/** + * Attempts to call the {@code then} method on an object in the hopes that it is + * a Promise-compatible instance. This allows interoperation between different + * Promise implementations, however a non-compliant object may cause a Promise + * to hang indefinitely. If the {@code then} method throws an exception, the + * dependent Promise will be rejected with the thrown value. + * + * @see http://promisesaplus.com/#point-70 + * + * @param {Thenable} thenable An object with a {@code then} method that may be + * compatible with the Promise/A+ specification. + * @param {!Function} then The {@code then} method of the Thenable object. + * @private + */ +goog.Promise.prototype.tryThen_ = function(thenable, then) { + this.state_ = goog.Promise.State_.BLOCKED; + var promise = this; + var called = false; + + var resolve = function(value) { + if (!called) { + called = true; + promise.unblockAndFulfill_(value); + } + }; + + var reject = function(reason) { + if (!called) { + called = true; + promise.unblockAndReject_(reason); + } + }; + + try { + then.call(thenable, resolve, reject); + } catch (e) { + reject(e); + } +}; + + +/** + * Executes the pending callbacks of a resolved Promise after a timeout. + * + * Section 2.2.4 of the Promises/A+ specification requires that Promise + * callbacks must only be invoked from a call stack that only contains Promise + * implementation code, which we accomplish by invoking callback execution after + * a timeout. If {@code startExecution_} is called multiple times for the same + * Promise, the callback chain will be evaluated only once. Additional callbacks + * may be added during the evaluation phase, and will be executed in the same + * event loop. + * + * All Promises added to the waiting list during the same browser event loop + * will be executed in one batch to avoid using a separate timeout per Promise. + * + * @private + */ +goog.Promise.prototype.scheduleCallbacks_ = function() { + if (!this.executing_) { + this.executing_ = true; + goog.async.run(this.executeCallbacks_, this); + } +}; + + +/** + * Executes all pending callbacks for this Promise. + * + * @private + */ +goog.Promise.prototype.executeCallbacks_ = function() { + while (this.callbackEntries_ && this.callbackEntries_.length) { + var entries = this.callbackEntries_; + this.callbackEntries_ = []; + + for (var i = 0; i < entries.length; i++) { + if (goog.Promise.LONG_STACK_TRACES) { + this.currentStep_++; + } + this.executeCallback_(entries[i], this.state_, this.result_); + } + } + this.executing_ = false; +}; + + +/** + * Executes a pending callback for this Promise. Invokes an {@code onFulfilled} + * or {@code onRejected} callback based on the resolved state of the Promise. + * + * @param {!goog.Promise.CallbackEntry_} callbackEntry An entry containing the + * onFulfilled and/or onRejected callbacks for this step. + * @param {goog.Promise.State_} state The resolution status of the Promise, + * either FULFILLED or REJECTED. + * @param {*} result The resolved result of the Promise. + * @private + */ +goog.Promise.prototype.executeCallback_ = function( + callbackEntry, state, result) { + if (state == goog.Promise.State_.FULFILLED) { + callbackEntry.onFulfilled(result); + } else { + this.removeUnhandledRejection_(); + callbackEntry.onRejected(result); + } +}; + + +/** + * Records a stack trace entry for functions that call {@code then} or the + * Promise constructor. May be disabled by unsetting {@code LONG_STACK_TRACES}. + * + * @param {!Error} err An Error object created by the calling function for + * providing a stack trace. + * @private + */ +goog.Promise.prototype.addStackTrace_ = function(err) { + if (goog.Promise.LONG_STACK_TRACES && goog.isString(err.stack)) { + // Extract the third line of the stack trace, which is the entry for the + // user function that called into Promise code. + var trace = err.stack.split('\n', 4)[3]; + var message = err.message; + + // Pad the message to align the traces. + message += Array(11 - message.length).join(' '); + this.stack_.push(message + trace); + } +}; + + +/** + * Adds extra stack trace information to an exception for the list of + * asynchronous {@code then} calls that have been run for this Promise. Stack + * trace information is recorded in {@see #addStackTrace_}, and appended to + * rethrown errors when {@code LONG_STACK_TRACES} is enabled. + * + * @param {*} err An unhandled exception captured during callback execution. + * @private + */ +goog.Promise.prototype.appendLongStack_ = function(err) { + if (goog.Promise.LONG_STACK_TRACES && + err && goog.isString(err.stack) && this.stack_.length) { + var longTrace = ['Promise trace:']; + + for (var promise = this; promise; promise = promise.parent_) { + for (var i = this.currentStep_; i >= 0; i--) { + longTrace.push(promise.stack_[i]); + } + longTrace.push('Value: ' + + '[' + (promise.state_ == goog.Promise.State_.REJECTED ? + 'REJECTED' : 'FULFILLED') + '] ' + + '<' + String(promise.result_) + '>'); + } + err.stack += '\n\n' + longTrace.join('\n'); + } +}; + + +/** + * Marks this rejected Promise as having being handled. Also marks any parent + * Promises in the rejected state as handled. The rejection handler will no + * longer be invoked for this Promise (if it has not been called already). + * + * @private + */ +goog.Promise.prototype.removeUnhandledRejection_ = function() { + if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) { + for (var p = this; p && p.unhandledRejectionId_; p = p.parent_) { + goog.global.clearTimeout(p.unhandledRejectionId_); + p.unhandledRejectionId_ = 0; + } + } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) { + for (var p = this; p && p.hadUnhandledRejection_; p = p.parent_) { + p.hadUnhandledRejection_ = false; + } + } +}; + + +/** + * Marks this rejected Promise as unhandled. If no {@code onRejected} callback + * is called for this Promise before the {@code UNHANDLED_REJECTION_DELAY} + * expires, the reason will be passed to the unhandled rejection handler. The + * handler typically rethrows the rejection reason so that it becomes visible in + * the developer console. + * + * @param {!goog.Promise} promise The rejected Promise. + * @param {*} reason The Promise rejection reason. + * @private + */ +goog.Promise.addUnhandledRejection_ = function(promise, reason) { + if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) { + promise.unhandledRejectionId_ = goog.global.setTimeout(function() { + promise.appendLongStack_(reason); + goog.Promise.handleRejection_.call(null, reason); + }, goog.Promise.UNHANDLED_REJECTION_DELAY); + + } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) { + promise.hadUnhandledRejection_ = true; + goog.async.run(function() { + if (promise.hadUnhandledRejection_) { + promise.appendLongStack_(reason); + goog.Promise.handleRejection_.call(null, reason); + } + }); + } +}; + + +/** + * A method that is invoked with the rejection reasons for Promises that are + * rejected but have no {@code onRejected} callbacks registered yet. + * @type {function(*)} + * @private + */ +goog.Promise.handleRejection_ = goog.async.throwException; + + +/** + * Sets a handler that will be called with reasons from unhandled rejected + * Promises. If the rejected Promise (or one of its descendants) has an + * {@code onRejected} callback registered, the rejection will be considered + * handled, and the rejection handler will not be called. + * + * By default, unhandled rejections are rethrown so that the error may be + * captured by the developer console or a {@code window.onerror} handler. + * + * @param {function(*)} handler A function that will be called with reasons from + * rejected Promises. Defaults to {@code goog.async.throwException}. + */ +goog.Promise.setUnhandledRejectionHandler = function(handler) { + goog.Promise.handleRejection_ = handler; +}; + + + +/** + * Error used as a rejection reason for canceled Promises. + * + * @param {string=} opt_message + * @constructor + * @extends {goog.debug.Error} + * @final + */ +goog.Promise.CancellationError = function(opt_message) { + goog.Promise.CancellationError.base(this, 'constructor', opt_message); +}; +goog.inherits(goog.Promise.CancellationError, goog.debug.Error); + + +/** @override */ +goog.Promise.CancellationError.prototype.name = 'cancel'; + + + +/** + * Internal implementation of the resolver interface. + * + * @param {!goog.Promise.<TYPE>} promise + * @param {function((TYPE|goog.Promise.<TYPE>|Thenable)=)} resolve + * @param {function(*): void} reject + * @implements {goog.promise.Resolver.<TYPE>} + * @final @struct + * @constructor + * @private + * @template TYPE + */ +goog.Promise.Resolver_ = function(promise, resolve, reject) { + /** @const */ + this.promise = promise; + + /** @const */ + this.resolve = resolve; + + /** @const */ + this.reject = reject; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/resolver.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/resolver.js new file mode 100644 index 0000000..e27c300 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/resolver.js @@ -0,0 +1,48 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +goog.provide('goog.promise.Resolver'); + + + +/** + * Resolver interface for promises. The resolver is a convenience interface that + * bundles the promise and its associated resolve and reject functions together, + * for cases where the resolver needs to be persisted internally. + * + * @interface + * @template TYPE + */ +goog.promise.Resolver = function() {}; + + +/** + * The promise that created this resolver. + * @const {!goog.Promise.<TYPE>} + */ +goog.promise.Resolver.prototype.promise; + + +/** + * Resolves this resolver with the specified value. + * @const {function((TYPE|goog.Promise.<TYPE>|Thenable)=)} + */ +goog.promise.Resolver.prototype.resolve; + + +/** + * Rejects this resolver with the specified reason. + * @const {function(*): void} + */ +goog.promise.Resolver.prototype.reject; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/thenable.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/thenable.js new file mode 100644 index 0000000..dde8ec8 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/thenable.js @@ -0,0 +1,111 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +goog.provide('goog.Thenable'); + + + +/** + * Provides a more strict interface for Thenables in terms of + * http://promisesaplus.com for interop with {@see goog.Promise}. + * + * @interface + * @extends {IThenable.<TYPE>} + * @template TYPE + */ +goog.Thenable = function() {}; + + +/** + * Adds callbacks that will operate on the result of the Thenable, returning a + * new child Promise. + * + * If the Thenable is fulfilled, the {@code onFulfilled} callback will be + * invoked with the fulfillment value as argument, and the child Promise will + * be fulfilled with the return value of the callback. If the callback throws + * an exception, the child Promise will be rejected with the thrown value + * instead. + * + * If the Thenable is rejected, the {@code onRejected} callback will be invoked + * with the rejection reason as argument, and the child Promise will be rejected + * with the return value of the callback or thrown value. + * + * @param {?(function(this:THIS, TYPE): + * (RESULT|IThenable.<RESULT>|Thenable))=} opt_onFulfilled A + * function that will be invoked with the fulfillment value if the Promise + * is fullfilled. + * @param {?(function(this:THIS, *): *)=} opt_onRejected A function that will + * be invoked with the rejection reason if the Promise is rejected. + * @param {THIS=} opt_context An optional context object that will be the + * execution context for the callbacks. By default, functions are executed + * with the default this. + * @return {!goog.Promise.<RESULT>} A new Promise that will receive the result + * of the fulfillment or rejection callback. + * @template RESULT,THIS + */ +goog.Thenable.prototype.then = function(opt_onFulfilled, opt_onRejected, + opt_context) {}; + + +/** + * An expando property to indicate that an object implements + * {@code goog.Thenable}. + * + * {@see addImplementation}. + * + * @const + */ +goog.Thenable.IMPLEMENTED_BY_PROP = '$goog_Thenable'; + + +/** + * Marks a given class (constructor) as an implementation of Thenable, so + * that we can query that fact at runtime. The class must have already + * implemented the interface. + * Exports a 'then' method on the constructor prototype, so that the objects + * also implement the extern {@see goog.Thenable} interface for interop with + * other Promise implementations. + * @param {function(new:goog.Thenable,...[?])} ctor The class constructor. The + * corresponding class must have already implemented the interface. + */ +goog.Thenable.addImplementation = function(ctor) { + goog.exportProperty(ctor.prototype, 'then', ctor.prototype.then); + if (COMPILED) { + ctor.prototype[goog.Thenable.IMPLEMENTED_BY_PROP] = true; + } else { + // Avoids dictionary access in uncompiled mode. + ctor.prototype.$goog_Thenable = true; + } +}; + + +/** + * @param {*} object + * @return {boolean} Whether a given instance implements {@code goog.Thenable}. + * The class/superclass of the instance must call {@code addImplementation}. + */ +goog.Thenable.isImplementedBy = function(object) { + if (!object) { + return false; + } + try { + if (COMPILED) { + return !!object[goog.Thenable.IMPLEMENTED_BY_PROP]; + } + return !!object.$goog_Thenable; + } catch (e) { + // Property access seems to be forbidden. + return false; + } +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/reflect/reflect.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/reflect/reflect.js new file mode 100644 index 0000000..df09e15 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/reflect/reflect.js @@ -0,0 +1,77 @@ +// Copyright 2009 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Useful compiler idioms. + * + */ + +goog.provide('goog.reflect'); + + +/** + * Syntax for object literal casts. + * @see http://go/jscompiler-renaming + * @see http://code.google.com/p/closure-compiler/wiki/ + * ExperimentalTypeBasedPropertyRenaming + * + * Use this if you have an object literal whose keys need to have the same names + * as the properties of some class even after they are renamed by the compiler. + * + * @param {!Function} type Type to cast to. + * @param {Object} object Object literal to cast. + * @return {Object} The object literal. + */ +goog.reflect.object = function(type, object) { + return object; +}; + + +/** + * To assert to the compiler that an operation is needed when it would + * otherwise be stripped. For example: + * <code> + * // Force a layout + * goog.reflect.sinkValue(dialog.offsetHeight); + * </code> + * @type {!Function} + */ +goog.reflect.sinkValue = function(x) { + goog.reflect.sinkValue[' '](x); + return x; +}; + + +/** + * The compiler should optimize this function away iff no one ever uses + * goog.reflect.sinkValue. + */ +goog.reflect.sinkValue[' '] = goog.nullFunction; + + +/** + * Check if a property can be accessed without throwing an exception. + * @param {Object} obj The owner of the property. + * @param {string} prop The property name. + * @return {boolean} Whether the property is accessible. Will also return true + * if obj is null. + */ +goog.reflect.canAccessProperty = function(obj, prop) { + /** @preserveTry */ + try { + goog.reflect.sinkValue(obj[prop]); + return true; + } catch (e) {} + return false; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/string/string.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/string/string.js new file mode 100644 index 0000000..90ee58c --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/string/string.js @@ -0,0 +1,1476 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utilities for string manipulation. + */ + + +/** + * Namespace for string utilities + */ +goog.provide('goog.string'); +goog.provide('goog.string.Unicode'); + + +/** + * @define {boolean} Enables HTML escaping of lowercase letter "e" which helps + * with detection of double-escaping as this letter is frequently used. + */ +goog.define('goog.string.DETECT_DOUBLE_ESCAPING', false); + + +/** + * Common Unicode string characters. + * @enum {string} + */ +goog.string.Unicode = { + NBSP: '\xa0' +}; + + +/** + * Fast prefix-checker. + * @param {string} str The string to check. + * @param {string} prefix A string to look for at the start of {@code str}. + * @return {boolean} True if {@code str} begins with {@code prefix}. + */ +goog.string.startsWith = function(str, prefix) { + return str.lastIndexOf(prefix, 0) == 0; +}; + + +/** + * Fast suffix-checker. + * @param {string} str The string to check. + * @param {string} suffix A string to look for at the end of {@code str}. + * @return {boolean} True if {@code str} ends with {@code suffix}. + */ +goog.string.endsWith = function(str, suffix) { + var l = str.length - suffix.length; + return l >= 0 && str.indexOf(suffix, l) == l; +}; + + +/** + * Case-insensitive prefix-checker. + * @param {string} str The string to check. + * @param {string} prefix A string to look for at the end of {@code str}. + * @return {boolean} True if {@code str} begins with {@code prefix} (ignoring + * case). + */ +goog.string.caseInsensitiveStartsWith = function(str, prefix) { + return goog.string.caseInsensitiveCompare( + prefix, str.substr(0, prefix.length)) == 0; +}; + + +/** + * Case-insensitive suffix-checker. + * @param {string} str The string to check. + * @param {string} suffix A string to look for at the end of {@code str}. + * @return {boolean} True if {@code str} ends with {@code suffix} (ignoring + * case). + */ +goog.string.caseInsensitiveEndsWith = function(str, suffix) { + return goog.string.caseInsensitiveCompare( + suffix, str.substr(str.length - suffix.length, suffix.length)) == 0; +}; + + +/** + * Case-insensitive equality checker. + * @param {string} str1 First string to check. + * @param {string} str2 Second string to check. + * @return {boolean} True if {@code str1} and {@code str2} are the same string, + * ignoring case. + */ +goog.string.caseInsensitiveEquals = function(str1, str2) { + return str1.toLowerCase() == str2.toLowerCase(); +}; + + +/** + * Does simple python-style string substitution. + * subs("foo%s hot%s", "bar", "dog") becomes "foobar hotdog". + * @param {string} str The string containing the pattern. + * @param {...*} var_args The items to substitute into the pattern. + * @return {string} A copy of {@code str} in which each occurrence of + * {@code %s} has been replaced an argument from {@code var_args}. + */ +goog.string.subs = function(str, var_args) { + var splitParts = str.split('%s'); + var returnString = ''; + + var subsArguments = Array.prototype.slice.call(arguments, 1); + while (subsArguments.length && + // Replace up to the last split part. We are inserting in the + // positions between split parts. + splitParts.length > 1) { + returnString += splitParts.shift() + subsArguments.shift(); + } + + return returnString + splitParts.join('%s'); // Join unused '%s' +}; + + +/** + * Converts multiple whitespace chars (spaces, non-breaking-spaces, new lines + * and tabs) to a single space, and strips leading and trailing whitespace. + * @param {string} str Input string. + * @return {string} A copy of {@code str} with collapsed whitespace. + */ +goog.string.collapseWhitespace = function(str) { + // Since IE doesn't include non-breaking-space (0xa0) in their \s character + // class (as required by section 7.2 of the ECMAScript spec), we explicitly + // include it in the regexp to enforce consistent cross-browser behavior. + return str.replace(/[\s\xa0]+/g, ' ').replace(/^\s+|\s+$/g, ''); +}; + + +/** + * Checks if a string is empty or contains only whitespaces. + * @param {string} str The string to check. + * @return {boolean} True if {@code str} is empty or whitespace only. + */ +goog.string.isEmpty = function(str) { + // testing length == 0 first is actually slower in all browsers (about the + // same in Opera). + // Since IE doesn't include non-breaking-space (0xa0) in their \s character + // class (as required by section 7.2 of the ECMAScript spec), we explicitly + // include it in the regexp to enforce consistent cross-browser behavior. + return /^[\s\xa0]*$/.test(str); +}; + + +/** + * Checks if a string is null, undefined, empty or contains only whitespaces. + * @param {*} str The string to check. + * @return {boolean} True if{@code str} is null, undefined, empty, or + * whitespace only. + */ +goog.string.isEmptySafe = function(str) { + return goog.string.isEmpty(goog.string.makeSafe(str)); +}; + + +/** + * Checks if a string is all breaking whitespace. + * @param {string} str The string to check. + * @return {boolean} Whether the string is all breaking whitespace. + */ +goog.string.isBreakingWhitespace = function(str) { + return !/[^\t\n\r ]/.test(str); +}; + + +/** + * Checks if a string contains all letters. + * @param {string} str string to check. + * @return {boolean} True if {@code str} consists entirely of letters. + */ +goog.string.isAlpha = function(str) { + return !/[^a-zA-Z]/.test(str); +}; + + +/** + * Checks if a string contains only numbers. + * @param {*} str string to check. If not a string, it will be + * casted to one. + * @return {boolean} True if {@code str} is numeric. + */ +goog.string.isNumeric = function(str) { + return !/[^0-9]/.test(str); +}; + + +/** + * Checks if a string contains only numbers or letters. + * @param {string} str string to check. + * @return {boolean} True if {@code str} is alphanumeric. + */ +goog.string.isAlphaNumeric = function(str) { + return !/[^a-zA-Z0-9]/.test(str); +}; + + +/** + * Checks if a character is a space character. + * @param {string} ch Character to check. + * @return {boolean} True if {code ch} is a space. + */ +goog.string.isSpace = function(ch) { + return ch == ' '; +}; + + +/** + * Checks if a character is a valid unicode character. + * @param {string} ch Character to check. + * @return {boolean} True if {code ch} is a valid unicode character. + */ +goog.string.isUnicodeChar = function(ch) { + return ch.length == 1 && ch >= ' ' && ch <= '~' || + ch >= '\u0080' && ch <= '\uFFFD'; +}; + + +/** + * Takes a string and replaces newlines with a space. Multiple lines are + * replaced with a single space. + * @param {string} str The string from which to strip newlines. + * @return {string} A copy of {@code str} stripped of newlines. + */ +goog.string.stripNewlines = function(str) { + return str.replace(/(\r\n|\r|\n)+/g, ' '); +}; + + +/** + * Replaces Windows and Mac new lines with unix style: \r or \r\n with \n. + * @param {string} str The string to in which to canonicalize newlines. + * @return {string} {@code str} A copy of {@code} with canonicalized newlines. + */ +goog.string.canonicalizeNewlines = function(str) { + return str.replace(/(\r\n|\r|\n)/g, '\n'); +}; + + +/** + * Normalizes whitespace in a string, replacing all whitespace chars with + * a space. + * @param {string} str The string in which to normalize whitespace. + * @return {string} A copy of {@code str} with all whitespace normalized. + */ +goog.string.normalizeWhitespace = function(str) { + return str.replace(/\xa0|\s/g, ' '); +}; + + +/** + * Normalizes spaces in a string, replacing all consecutive spaces and tabs + * with a single space. Replaces non-breaking space with a space. + * @param {string} str The string in which to normalize spaces. + * @return {string} A copy of {@code str} with all consecutive spaces and tabs + * replaced with a single space. + */ +goog.string.normalizeSpaces = function(str) { + return str.replace(/\xa0|[ \t]+/g, ' '); +}; + + +/** + * Removes the breaking spaces from the left and right of the string and + * collapses the sequences of breaking spaces in the middle into single spaces. + * The original and the result strings render the same way in HTML. + * @param {string} str A string in which to collapse spaces. + * @return {string} Copy of the string with normalized breaking spaces. + */ +goog.string.collapseBreakingSpaces = function(str) { + return str.replace(/[\t\r\n ]+/g, ' ').replace( + /^[\t\r\n ]+|[\t\r\n ]+$/g, ''); +}; + + +/** + * Trims white spaces to the left and right of a string. + * @param {string} str The string to trim. + * @return {string} A trimmed copy of {@code str}. + */ +goog.string.trim = function(str) { + // Since IE doesn't include non-breaking-space (0xa0) in their \s character + // class (as required by section 7.2 of the ECMAScript spec), we explicitly + // include it in the regexp to enforce consistent cross-browser behavior. + return str.replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''); +}; + + +/** + * Trims whitespaces at the left end of a string. + * @param {string} str The string to left trim. + * @return {string} A trimmed copy of {@code str}. + */ +goog.string.trimLeft = function(str) { + // Since IE doesn't include non-breaking-space (0xa0) in their \s character + // class (as required by section 7.2 of the ECMAScript spec), we explicitly + // include it in the regexp to enforce consistent cross-browser behavior. + return str.replace(/^[\s\xa0]+/, ''); +}; + + +/** + * Trims whitespaces at the right end of a string. + * @param {string} str The string to right trim. + * @return {string} A trimmed copy of {@code str}. + */ +goog.string.trimRight = function(str) { + // Since IE doesn't include non-breaking-space (0xa0) in their \s character + // class (as required by section 7.2 of the ECMAScript spec), we explicitly + // include it in the regexp to enforce consistent cross-browser behavior. + return str.replace(/[\s\xa0]+$/, ''); +}; + + +/** + * A string comparator that ignores case. + * -1 = str1 less than str2 + * 0 = str1 equals str2 + * 1 = str1 greater than str2 + * + * @param {string} str1 The string to compare. + * @param {string} str2 The string to compare {@code str1} to. + * @return {number} The comparator result, as described above. + */ +goog.string.caseInsensitiveCompare = function(str1, str2) { + var test1 = String(str1).toLowerCase(); + var test2 = String(str2).toLowerCase(); + + if (test1 < test2) { + return -1; + } else if (test1 == test2) { + return 0; + } else { + return 1; + } +}; + + +/** + * Regular expression used for splitting a string into substrings of fractional + * numbers, integers, and non-numeric characters. + * @type {RegExp} + * @private + */ +goog.string.numerateCompareRegExp_ = /(\.\d+)|(\d+)|(\D+)/g; + + +/** + * String comparison function that handles numbers in a way humans might expect. + * Using this function, the string "File 2.jpg" sorts before "File 10.jpg". The + * comparison is mostly case-insensitive, though strings that are identical + * except for case are sorted with the upper-case strings before lower-case. + * + * This comparison function is significantly slower (about 500x) than either + * the default or the case-insensitive compare. It should not be used in + * time-critical code, but should be fast enough to sort several hundred short + * strings (like filenames) with a reasonable delay. + * + * @param {string} str1 The string to compare in a numerically sensitive way. + * @param {string} str2 The string to compare {@code str1} to. + * @return {number} less than 0 if str1 < str2, 0 if str1 == str2, greater than + * 0 if str1 > str2. + */ +goog.string.numerateCompare = function(str1, str2) { + if (str1 == str2) { + return 0; + } + if (!str1) { + return -1; + } + if (!str2) { + return 1; + } + + // Using match to split the entire string ahead of time turns out to be faster + // for most inputs than using RegExp.exec or iterating over each character. + var tokens1 = str1.toLowerCase().match(goog.string.numerateCompareRegExp_); + var tokens2 = str2.toLowerCase().match(goog.string.numerateCompareRegExp_); + + var count = Math.min(tokens1.length, tokens2.length); + + for (var i = 0; i < count; i++) { + var a = tokens1[i]; + var b = tokens2[i]; + + // Compare pairs of tokens, returning if one token sorts before the other. + if (a != b) { + + // Only if both tokens are integers is a special comparison required. + // Decimal numbers are sorted as strings (e.g., '.09' < '.1'). + var num1 = parseInt(a, 10); + if (!isNaN(num1)) { + var num2 = parseInt(b, 10); + if (!isNaN(num2) && num1 - num2) { + return num1 - num2; + } + } + return a < b ? -1 : 1; + } + } + + // If one string is a substring of the other, the shorter string sorts first. + if (tokens1.length != tokens2.length) { + return tokens1.length - tokens2.length; + } + + // The two strings must be equivalent except for case (perfect equality is + // tested at the head of the function.) Revert to default ASCII-betical string + // comparison to stablize the sort. + return str1 < str2 ? -1 : 1; +}; + + +/** + * URL-encodes a string + * @param {*} str The string to url-encode. + * @return {string} An encoded copy of {@code str} that is safe for urls. + * Note that '#', ':', and other characters used to delimit portions + * of URLs *will* be encoded. + */ +goog.string.urlEncode = function(str) { + return encodeURIComponent(String(str)); +}; + + +/** + * URL-decodes the string. We need to specially handle '+'s because + * the javascript library doesn't convert them to spaces. + * @param {string} str The string to url decode. + * @return {string} The decoded {@code str}. + */ +goog.string.urlDecode = function(str) { + return decodeURIComponent(str.replace(/\+/g, ' ')); +}; + + +/** + * Converts \n to <br>s or <br />s. + * @param {string} str The string in which to convert newlines. + * @param {boolean=} opt_xml Whether to use XML compatible tags. + * @return {string} A copy of {@code str} with converted newlines. + */ +goog.string.newLineToBr = function(str, opt_xml) { + return str.replace(/(\r\n|\r|\n)/g, opt_xml ? '<br />' : '<br>'); +}; + + +/** + * Escapes double quote '"' and single quote '\'' characters in addition to + * '&', '<', and '>' so that a string can be included in an HTML tag attribute + * value within double or single quotes. + * + * It should be noted that > doesn't need to be escaped for the HTML or XML to + * be valid, but it has been decided to escape it for consistency with other + * implementations. + * + * With goog.string.DETECT_DOUBLE_ESCAPING, this function escapes also the + * lowercase letter "e". + * + * NOTE(user): + * HtmlEscape is often called during the generation of large blocks of HTML. + * Using statics for the regular expressions and strings is an optimization + * that can more than half the amount of time IE spends in this function for + * large apps, since strings and regexes both contribute to GC allocations. + * + * Testing for the presence of a character before escaping increases the number + * of function calls, but actually provides a speed increase for the average + * case -- since the average case often doesn't require the escaping of all 4 + * characters and indexOf() is much cheaper than replace(). + * The worst case does suffer slightly from the additional calls, therefore the + * opt_isLikelyToContainHtmlChars option has been included for situations + * where all 4 HTML entities are very likely to be present and need escaping. + * + * Some benchmarks (times tended to fluctuate +-0.05ms): + * FireFox IE6 + * (no chars / average (mix of cases) / all 4 chars) + * no checks 0.13 / 0.22 / 0.22 0.23 / 0.53 / 0.80 + * indexOf 0.08 / 0.17 / 0.26 0.22 / 0.54 / 0.84 + * indexOf + re test 0.07 / 0.17 / 0.28 0.19 / 0.50 / 0.85 + * + * An additional advantage of checking if replace actually needs to be called + * is a reduction in the number of object allocations, so as the size of the + * application grows the difference between the various methods would increase. + * + * @param {string} str string to be escaped. + * @param {boolean=} opt_isLikelyToContainHtmlChars Don't perform a check to see + * if the character needs replacing - use this option if you expect each of + * the characters to appear often. Leave false if you expect few html + * characters to occur in your strings, such as if you are escaping HTML. + * @return {string} An escaped copy of {@code str}. + */ +goog.string.htmlEscape = function(str, opt_isLikelyToContainHtmlChars) { + + if (opt_isLikelyToContainHtmlChars) { + str = str.replace(goog.string.AMP_RE_, '&') + .replace(goog.string.LT_RE_, '<') + .replace(goog.string.GT_RE_, '>') + .replace(goog.string.QUOT_RE_, '"') + .replace(goog.string.SINGLE_QUOTE_RE_, ''') + .replace(goog.string.NULL_RE_, '�'); + if (goog.string.DETECT_DOUBLE_ESCAPING) { + str = str.replace(goog.string.E_RE_, 'e'); + } + return str; + + } else { + // quick test helps in the case when there are no chars to replace, in + // worst case this makes barely a difference to the time taken + if (!goog.string.ALL_RE_.test(str)) return str; + + // str.indexOf is faster than regex.test in this case + if (str.indexOf('&') != -1) { + str = str.replace(goog.string.AMP_RE_, '&'); + } + if (str.indexOf('<') != -1) { + str = str.replace(goog.string.LT_RE_, '<'); + } + if (str.indexOf('>') != -1) { + str = str.replace(goog.string.GT_RE_, '>'); + } + if (str.indexOf('"') != -1) { + str = str.replace(goog.string.QUOT_RE_, '"'); + } + if (str.indexOf('\'') != -1) { + str = str.replace(goog.string.SINGLE_QUOTE_RE_, '''); + } + if (str.indexOf('\x00') != -1) { + str = str.replace(goog.string.NULL_RE_, '�'); + } + if (goog.string.DETECT_DOUBLE_ESCAPING && str.indexOf('e') != -1) { + str = str.replace(goog.string.E_RE_, 'e'); + } + return str; + } +}; + + +/** + * Regular expression that matches an ampersand, for use in escaping. + * @const {!RegExp} + * @private + */ +goog.string.AMP_RE_ = /&/g; + + +/** + * Regular expression that matches a less than sign, for use in escaping. + * @const {!RegExp} + * @private + */ +goog.string.LT_RE_ = /</g; + + +/** + * Regular expression that matches a greater than sign, for use in escaping. + * @const {!RegExp} + * @private + */ +goog.string.GT_RE_ = />/g; + + +/** + * Regular expression that matches a double quote, for use in escaping. + * @const {!RegExp} + * @private + */ +goog.string.QUOT_RE_ = /"/g; + + +/** + * Regular expression that matches a single quote, for use in escaping. + * @const {!RegExp} + * @private + */ +goog.string.SINGLE_QUOTE_RE_ = /'/g; + + +/** + * Regular expression that matches null character, for use in escaping. + * @const {!RegExp} + * @private + */ +goog.string.NULL_RE_ = /\x00/g; + + +/** + * Regular expression that matches a lowercase letter "e", for use in escaping. + * @const {!RegExp} + * @private + */ +goog.string.E_RE_ = /e/g; + + +/** + * Regular expression that matches any character that needs to be escaped. + * @const {!RegExp} + * @private + */ +goog.string.ALL_RE_ = (goog.string.DETECT_DOUBLE_ESCAPING ? + /[\x00&<>"'e]/ : + /[\x00&<>"']/); + + +/** + * Unescapes an HTML string. + * + * @param {string} str The string to unescape. + * @return {string} An unescaped copy of {@code str}. + */ +goog.string.unescapeEntities = function(str) { + if (goog.string.contains(str, '&')) { + // We are careful not to use a DOM if we do not have one. We use the [] + // notation so that the JSCompiler will not complain about these objects and + // fields in the case where we have no DOM. + if ('document' in goog.global) { + return goog.string.unescapeEntitiesUsingDom_(str); + } else { + // Fall back on pure XML entities + return goog.string.unescapePureXmlEntities_(str); + } + } + return str; +}; + + +/** + * Unescapes a HTML string using the provided document. + * + * @param {string} str The string to unescape. + * @param {!Document} document A document to use in escaping the string. + * @return {string} An unescaped copy of {@code str}. + */ +goog.string.unescapeEntitiesWithDocument = function(str, document) { + if (goog.string.contains(str, '&')) { + return goog.string.unescapeEntitiesUsingDom_(str, document); + } + return str; +}; + + +/** + * Unescapes an HTML string using a DOM to resolve non-XML, non-numeric + * entities. This function is XSS-safe and whitespace-preserving. + * @private + * @param {string} str The string to unescape. + * @param {Document=} opt_document An optional document to use for creating + * elements. If this is not specified then the default window.document + * will be used. + * @return {string} The unescaped {@code str} string. + */ +goog.string.unescapeEntitiesUsingDom_ = function(str, opt_document) { + var seen = {'&': '&', '<': '<', '>': '>', '"': '"'}; + var div; + if (opt_document) { + div = opt_document.createElement('div'); + } else { + div = goog.global.document.createElement('div'); + } + // Match as many valid entity characters as possible. If the actual entity + // happens to be shorter, it will still work as innerHTML will return the + // trailing characters unchanged. Since the entity characters do not include + // open angle bracket, there is no chance of XSS from the innerHTML use. + // Since no whitespace is passed to innerHTML, whitespace is preserved. + return str.replace(goog.string.HTML_ENTITY_PATTERN_, function(s, entity) { + // Check for cached entity. + var value = seen[s]; + if (value) { + return value; + } + // Check for numeric entity. + if (entity.charAt(0) == '#') { + // Prefix with 0 so that hex entities (e.g. ) parse as hex numbers. + var n = Number('0' + entity.substr(1)); + if (!isNaN(n)) { + value = String.fromCharCode(n); + } + } + // Fall back to innerHTML otherwise. + if (!value) { + // Append a non-entity character to avoid a bug in Webkit that parses + // an invalid entity at the end of innerHTML text as the empty string. + div.innerHTML = s + ' '; + // Then remove the trailing character from the result. + value = div.firstChild.nodeValue.slice(0, -1); + } + // Cache and return. + return seen[s] = value; + }); +}; + + +/** + * Unescapes XML entities. + * @private + * @param {string} str The string to unescape. + * @return {string} An unescaped copy of {@code str}. + */ +goog.string.unescapePureXmlEntities_ = function(str) { + return str.replace(/&([^;]+);/g, function(s, entity) { + switch (entity) { + case 'amp': + return '&'; + case 'lt': + return '<'; + case 'gt': + return '>'; + case 'quot': + return '"'; + default: + if (entity.charAt(0) == '#') { + // Prefix with 0 so that hex entities (e.g. ) parse as hex. + var n = Number('0' + entity.substr(1)); + if (!isNaN(n)) { + return String.fromCharCode(n); + } + } + // For invalid entities we just return the entity + return s; + } + }); +}; + + +/** + * Regular expression that matches an HTML entity. + * See also HTML5: Tokenization / Tokenizing character references. + * @private + * @type {!RegExp} + */ +goog.string.HTML_ENTITY_PATTERN_ = /&([^;\s<&]+);?/g; + + +/** + * Do escaping of whitespace to preserve spatial formatting. We use character + * entity #160 to make it safer for xml. + * @param {string} str The string in which to escape whitespace. + * @param {boolean=} opt_xml Whether to use XML compatible tags. + * @return {string} An escaped copy of {@code str}. + */ +goog.string.whitespaceEscape = function(str, opt_xml) { + // This doesn't use goog.string.preserveSpaces for backwards compatibility. + return goog.string.newLineToBr(str.replace(/ /g, '  '), opt_xml); +}; + + +/** + * Preserve spaces that would be otherwise collapsed in HTML by replacing them + * with non-breaking space Unicode characters. + * @param {string} str The string in which to preserve whitespace. + * @return {string} A copy of {@code str} with preserved whitespace. + */ +goog.string.preserveSpaces = function(str) { + return str.replace(/(^|[\n ]) /g, '$1' + goog.string.Unicode.NBSP); +}; + + +/** + * Strip quote characters around a string. The second argument is a string of + * characters to treat as quotes. This can be a single character or a string of + * multiple character and in that case each of those are treated as possible + * quote characters. For example: + * + * <pre> + * goog.string.stripQuotes('"abc"', '"`') --> 'abc' + * goog.string.stripQuotes('`abc`', '"`') --> 'abc' + * </pre> + * + * @param {string} str The string to strip. + * @param {string} quoteChars The quote characters to strip. + * @return {string} A copy of {@code str} without the quotes. + */ +goog.string.stripQuotes = function(str, quoteChars) { + var length = quoteChars.length; + for (var i = 0; i < length; i++) { + var quoteChar = length == 1 ? quoteChars : quoteChars.charAt(i); + if (str.charAt(0) == quoteChar && str.charAt(str.length - 1) == quoteChar) { + return str.substring(1, str.length - 1); + } + } + return str; +}; + + +/** + * Truncates a string to a certain length and adds '...' if necessary. The + * length also accounts for the ellipsis, so a maximum length of 10 and a string + * 'Hello World!' produces 'Hello W...'. + * @param {string} str The string to truncate. + * @param {number} chars Max number of characters. + * @param {boolean=} opt_protectEscapedCharacters Whether to protect escaped + * characters from being cut off in the middle. + * @return {string} The truncated {@code str} string. + */ +goog.string.truncate = function(str, chars, opt_protectEscapedCharacters) { + if (opt_protectEscapedCharacters) { + str = goog.string.unescapeEntities(str); + } + + if (str.length > chars) { + str = str.substring(0, chars - 3) + '...'; + } + + if (opt_protectEscapedCharacters) { + str = goog.string.htmlEscape(str); + } + + return str; +}; + + +/** + * Truncate a string in the middle, adding "..." if necessary, + * and favoring the beginning of the string. + * @param {string} str The string to truncate the middle of. + * @param {number} chars Max number of characters. + * @param {boolean=} opt_protectEscapedCharacters Whether to protect escaped + * characters from being cutoff in the middle. + * @param {number=} opt_trailingChars Optional number of trailing characters to + * leave at the end of the string, instead of truncating as close to the + * middle as possible. + * @return {string} A truncated copy of {@code str}. + */ +goog.string.truncateMiddle = function(str, chars, + opt_protectEscapedCharacters, opt_trailingChars) { + if (opt_protectEscapedCharacters) { + str = goog.string.unescapeEntities(str); + } + + if (opt_trailingChars && str.length > chars) { + if (opt_trailingChars > chars) { + opt_trailingChars = chars; + } + var endPoint = str.length - opt_trailingChars; + var startPoint = chars - opt_trailingChars; + str = str.substring(0, startPoint) + '...' + str.substring(endPoint); + } else if (str.length > chars) { + // Favor the beginning of the string: + var half = Math.floor(chars / 2); + var endPos = str.length - half; + half += chars % 2; + str = str.substring(0, half) + '...' + str.substring(endPos); + } + + if (opt_protectEscapedCharacters) { + str = goog.string.htmlEscape(str); + } + + return str; +}; + + +/** + * Special chars that need to be escaped for goog.string.quote. + * @private + * @type {Object} + */ +goog.string.specialEscapeChars_ = { + '\0': '\\0', + '\b': '\\b', + '\f': '\\f', + '\n': '\\n', + '\r': '\\r', + '\t': '\\t', + '\x0B': '\\x0B', // '\v' is not supported in JScript + '"': '\\"', + '\\': '\\\\' +}; + + +/** + * Character mappings used internally for goog.string.escapeChar. + * @private + * @type {Object} + */ +goog.string.jsEscapeCache_ = { + '\'': '\\\'' +}; + + +/** + * Encloses a string in double quotes and escapes characters so that the + * string is a valid JS string. + * @param {string} s The string to quote. + * @return {string} A copy of {@code s} surrounded by double quotes. + */ +goog.string.quote = function(s) { + s = String(s); + if (s.quote) { + return s.quote(); + } else { + var sb = ['"']; + for (var i = 0; i < s.length; i++) { + var ch = s.charAt(i); + var cc = ch.charCodeAt(0); + sb[i + 1] = goog.string.specialEscapeChars_[ch] || + ((cc > 31 && cc < 127) ? ch : goog.string.escapeChar(ch)); + } + sb.push('"'); + return sb.join(''); + } +}; + + +/** + * Takes a string and returns the escaped string for that character. + * @param {string} str The string to escape. + * @return {string} An escaped string representing {@code str}. + */ +goog.string.escapeString = function(str) { + var sb = []; + for (var i = 0; i < str.length; i++) { + sb[i] = goog.string.escapeChar(str.charAt(i)); + } + return sb.join(''); +}; + + +/** + * Takes a character and returns the escaped string for that character. For + * example escapeChar(String.fromCharCode(15)) -> "\\x0E". + * @param {string} c The character to escape. + * @return {string} An escaped string representing {@code c}. + */ +goog.string.escapeChar = function(c) { + if (c in goog.string.jsEscapeCache_) { + return goog.string.jsEscapeCache_[c]; + } + + if (c in goog.string.specialEscapeChars_) { + return goog.string.jsEscapeCache_[c] = goog.string.specialEscapeChars_[c]; + } + + var rv = c; + var cc = c.charCodeAt(0); + if (cc > 31 && cc < 127) { + rv = c; + } else { + // tab is 9 but handled above + if (cc < 256) { + rv = '\\x'; + if (cc < 16 || cc > 256) { + rv += '0'; + } + } else { + rv = '\\u'; + if (cc < 4096) { // \u1000 + rv += '0'; + } + } + rv += cc.toString(16).toUpperCase(); + } + + return goog.string.jsEscapeCache_[c] = rv; +}; + + +/** + * Takes a string and creates a map (Object) in which the keys are the + * characters in the string. The value for the key is set to true. You can + * then use goog.object.map or goog.array.map to change the values. + * @param {string} s The string to build the map from. + * @return {!Object} The map of characters used. + */ +// TODO(arv): It seems like we should have a generic goog.array.toMap. But do +// we want a dependency on goog.array in goog.string? +goog.string.toMap = function(s) { + var rv = {}; + for (var i = 0; i < s.length; i++) { + rv[s.charAt(i)] = true; + } + return rv; +}; + + +/** + * Determines whether a string contains a substring. + * @param {string} str The string to search. + * @param {string} subString The substring to search for. + * @return {boolean} Whether {@code str} contains {@code subString}. + */ +goog.string.contains = function(str, subString) { + return str.indexOf(subString) != -1; +}; + + +/** + * Determines whether a string contains a substring, ignoring case. + * @param {string} str The string to search. + * @param {string} subString The substring to search for. + * @return {boolean} Whether {@code str} contains {@code subString}. + */ +goog.string.caseInsensitiveContains = function(str, subString) { + return goog.string.contains(str.toLowerCase(), subString.toLowerCase()); +}; + + +/** + * Returns the non-overlapping occurrences of ss in s. + * If either s or ss evalutes to false, then returns zero. + * @param {string} s The string to look in. + * @param {string} ss The string to look for. + * @return {number} Number of occurrences of ss in s. + */ +goog.string.countOf = function(s, ss) { + return s && ss ? s.split(ss).length - 1 : 0; +}; + + +/** + * Removes a substring of a specified length at a specific + * index in a string. + * @param {string} s The base string from which to remove. + * @param {number} index The index at which to remove the substring. + * @param {number} stringLength The length of the substring to remove. + * @return {string} A copy of {@code s} with the substring removed or the full + * string if nothing is removed or the input is invalid. + */ +goog.string.removeAt = function(s, index, stringLength) { + var resultStr = s; + // If the index is greater or equal to 0 then remove substring + if (index >= 0 && index < s.length && stringLength > 0) { + resultStr = s.substr(0, index) + + s.substr(index + stringLength, s.length - index - stringLength); + } + return resultStr; +}; + + +/** + * Removes the first occurrence of a substring from a string. + * @param {string} s The base string from which to remove. + * @param {string} ss The string to remove. + * @return {string} A copy of {@code s} with {@code ss} removed or the full + * string if nothing is removed. + */ +goog.string.remove = function(s, ss) { + var re = new RegExp(goog.string.regExpEscape(ss), ''); + return s.replace(re, ''); +}; + + +/** + * Removes all occurrences of a substring from a string. + * @param {string} s The base string from which to remove. + * @param {string} ss The string to remove. + * @return {string} A copy of {@code s} with {@code ss} removed or the full + * string if nothing is removed. + */ +goog.string.removeAll = function(s, ss) { + var re = new RegExp(goog.string.regExpEscape(ss), 'g'); + return s.replace(re, ''); +}; + + +/** + * Escapes characters in the string that are not safe to use in a RegExp. + * @param {*} s The string to escape. If not a string, it will be casted + * to one. + * @return {string} A RegExp safe, escaped copy of {@code s}. + */ +goog.string.regExpEscape = function(s) { + return String(s).replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). + replace(/\x08/g, '\\x08'); +}; + + +/** + * Repeats a string n times. + * @param {string} string The string to repeat. + * @param {number} length The number of times to repeat. + * @return {string} A string containing {@code length} repetitions of + * {@code string}. + */ +goog.string.repeat = function(string, length) { + return new Array(length + 1).join(string); +}; + + +/** + * Pads number to given length and optionally rounds it to a given precision. + * For example: + * <pre>padNumber(1.25, 2, 3) -> '01.250' + * padNumber(1.25, 2) -> '01.25' + * padNumber(1.25, 2, 1) -> '01.3' + * padNumber(1.25, 0) -> '1.25'</pre> + * + * @param {number} num The number to pad. + * @param {number} length The desired length. + * @param {number=} opt_precision The desired precision. + * @return {string} {@code num} as a string with the given options. + */ +goog.string.padNumber = function(num, length, opt_precision) { + var s = goog.isDef(opt_precision) ? num.toFixed(opt_precision) : String(num); + var index = s.indexOf('.'); + if (index == -1) { + index = s.length; + } + return goog.string.repeat('0', Math.max(0, length - index)) + s; +}; + + +/** + * Returns a string representation of the given object, with + * null and undefined being returned as the empty string. + * + * @param {*} obj The object to convert. + * @return {string} A string representation of the {@code obj}. + */ +goog.string.makeSafe = function(obj) { + return obj == null ? '' : String(obj); +}; + + +/** + * Concatenates string expressions. This is useful + * since some browsers are very inefficient when it comes to using plus to + * concat strings. Be careful when using null and undefined here since + * these will not be included in the result. If you need to represent these + * be sure to cast the argument to a String first. + * For example: + * <pre>buildString('a', 'b', 'c', 'd') -> 'abcd' + * buildString(null, undefined) -> '' + * </pre> + * @param {...*} var_args A list of strings to concatenate. If not a string, + * it will be casted to one. + * @return {string} The concatenation of {@code var_args}. + */ +goog.string.buildString = function(var_args) { + return Array.prototype.join.call(arguments, ''); +}; + + +/** + * Returns a string with at least 64-bits of randomness. + * + * Doesn't trust Javascript's random function entirely. Uses a combination of + * random and current timestamp, and then encodes the string in base-36 to + * make it shorter. + * + * @return {string} A random string, e.g. sn1s7vb4gcic. + */ +goog.string.getRandomString = function() { + var x = 2147483648; + return Math.floor(Math.random() * x).toString(36) + + Math.abs(Math.floor(Math.random() * x) ^ goog.now()).toString(36); +}; + + +/** + * Compares two version numbers. + * + * @param {string|number} version1 Version of first item. + * @param {string|number} version2 Version of second item. + * + * @return {number} 1 if {@code version1} is higher. + * 0 if arguments are equal. + * -1 if {@code version2} is higher. + */ +goog.string.compareVersions = function(version1, version2) { + var order = 0; + // Trim leading and trailing whitespace and split the versions into + // subversions. + var v1Subs = goog.string.trim(String(version1)).split('.'); + var v2Subs = goog.string.trim(String(version2)).split('.'); + var subCount = Math.max(v1Subs.length, v2Subs.length); + + // Iterate over the subversions, as long as they appear to be equivalent. + for (var subIdx = 0; order == 0 && subIdx < subCount; subIdx++) { + var v1Sub = v1Subs[subIdx] || ''; + var v2Sub = v2Subs[subIdx] || ''; + + // Split the subversions into pairs of numbers and qualifiers (like 'b'). + // Two different RegExp objects are needed because they are both using + // the 'g' flag. + var v1CompParser = new RegExp('(\\d*)(\\D*)', 'g'); + var v2CompParser = new RegExp('(\\d*)(\\D*)', 'g'); + do { + var v1Comp = v1CompParser.exec(v1Sub) || ['', '', '']; + var v2Comp = v2CompParser.exec(v2Sub) || ['', '', '']; + // Break if there are no more matches. + if (v1Comp[0].length == 0 && v2Comp[0].length == 0) { + break; + } + + // Parse the numeric part of the subversion. A missing number is + // equivalent to 0. + var v1CompNum = v1Comp[1].length == 0 ? 0 : parseInt(v1Comp[1], 10); + var v2CompNum = v2Comp[1].length == 0 ? 0 : parseInt(v2Comp[1], 10); + + // Compare the subversion components. The number has the highest + // precedence. Next, if the numbers are equal, a subversion without any + // qualifier is always higher than a subversion with any qualifier. Next, + // the qualifiers are compared as strings. + order = goog.string.compareElements_(v1CompNum, v2CompNum) || + goog.string.compareElements_(v1Comp[2].length == 0, + v2Comp[2].length == 0) || + goog.string.compareElements_(v1Comp[2], v2Comp[2]); + // Stop as soon as an inequality is discovered. + } while (order == 0); + } + + return order; +}; + + +/** + * Compares elements of a version number. + * + * @param {string|number|boolean} left An element from a version number. + * @param {string|number|boolean} right An element from a version number. + * + * @return {number} 1 if {@code left} is higher. + * 0 if arguments are equal. + * -1 if {@code right} is higher. + * @private + */ +goog.string.compareElements_ = function(left, right) { + if (left < right) { + return -1; + } else if (left > right) { + return 1; + } + return 0; +}; + + +/** + * Maximum value of #goog.string.hashCode, exclusive. 2^32. + * @type {number} + * @private + */ +goog.string.HASHCODE_MAX_ = 0x100000000; + + +/** + * String hash function similar to java.lang.String.hashCode(). + * The hash code for a string is computed as + * s[0] * 31 ^ (n - 1) + s[1] * 31 ^ (n - 2) + ... + s[n - 1], + * where s[i] is the ith character of the string and n is the length of + * the string. We mod the result to make it between 0 (inclusive) and 2^32 + * (exclusive). + * @param {string} str A string. + * @return {number} Hash value for {@code str}, between 0 (inclusive) and 2^32 + * (exclusive). The empty string returns 0. + */ +goog.string.hashCode = function(str) { + var result = 0; + for (var i = 0; i < str.length; ++i) { + result = 31 * result + str.charCodeAt(i); + // Normalize to 4 byte range, 0 ... 2^32. + result %= goog.string.HASHCODE_MAX_; + } + return result; +}; + + +/** + * The most recent unique ID. |0 is equivalent to Math.floor in this case. + * @type {number} + * @private + */ +goog.string.uniqueStringCounter_ = Math.random() * 0x80000000 | 0; + + +/** + * Generates and returns a string which is unique in the current document. + * This is useful, for example, to create unique IDs for DOM elements. + * @return {string} A unique id. + */ +goog.string.createUniqueString = function() { + return 'goog_' + goog.string.uniqueStringCounter_++; +}; + + +/** + * Converts the supplied string to a number, which may be Infinity or NaN. + * This function strips whitespace: (toNumber(' 123') === 123) + * This function accepts scientific notation: (toNumber('1e1') === 10) + * + * This is better than Javascript's built-in conversions because, sadly: + * (Number(' ') === 0) and (parseFloat('123a') === 123) + * + * @param {string} str The string to convert. + * @return {number} The number the supplied string represents, or NaN. + */ +goog.string.toNumber = function(str) { + var num = Number(str); + if (num == 0 && goog.string.isEmpty(str)) { + return NaN; + } + return num; +}; + + +/** + * Returns whether the given string is lower camel case (e.g. "isFooBar"). + * + * Note that this assumes the string is entirely letters. + * @see http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms + * + * @param {string} str String to test. + * @return {boolean} Whether the string is lower camel case. + */ +goog.string.isLowerCamelCase = function(str) { + return /^[a-z]+([A-Z][a-z]*)*$/.test(str); +}; + + +/** + * Returns whether the given string is upper camel case (e.g. "FooBarBaz"). + * + * Note that this assumes the string is entirely letters. + * @see http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms + * + * @param {string} str String to test. + * @return {boolean} Whether the string is upper camel case. + */ +goog.string.isUpperCamelCase = function(str) { + return /^([A-Z][a-z]*)+$/.test(str); +}; + + +/** + * Converts a string from selector-case to camelCase (e.g. from + * "multi-part-string" to "multiPartString"), useful for converting + * CSS selectors and HTML dataset keys to their equivalent JS properties. + * @param {string} str The string in selector-case form. + * @return {string} The string in camelCase form. + */ +goog.string.toCamelCase = function(str) { + return String(str).replace(/\-([a-z])/g, function(all, match) { + return match.toUpperCase(); + }); +}; + + +/** + * Converts a string from camelCase to selector-case (e.g. from + * "multiPartString" to "multi-part-string"), useful for converting JS + * style and dataset properties to equivalent CSS selectors and HTML keys. + * @param {string} str The string in camelCase form. + * @return {string} The string in selector-case form. + */ +goog.string.toSelectorCase = function(str) { + return String(str).replace(/([A-Z])/g, '-$1').toLowerCase(); +}; + + +/** + * Converts a string into TitleCase. First character of the string is always + * capitalized in addition to the first letter of every subsequent word. + * Words are delimited by one or more whitespaces by default. Custom delimiters + * can optionally be specified to replace the default, which doesn't preserve + * whitespace delimiters and instead must be explicitly included if needed. + * + * Default delimiter => " ": + * goog.string.toTitleCase('oneTwoThree') => 'OneTwoThree' + * goog.string.toTitleCase('one two three') => 'One Two Three' + * goog.string.toTitleCase(' one two ') => ' One Two ' + * goog.string.toTitleCase('one_two_three') => 'One_two_three' + * goog.string.toTitleCase('one-two-three') => 'One-two-three' + * + * Custom delimiter => "_-.": + * goog.string.toTitleCase('oneTwoThree', '_-.') => 'OneTwoThree' + * goog.string.toTitleCase('one two three', '_-.') => 'One two three' + * goog.string.toTitleCase(' one two ', '_-.') => ' one two ' + * goog.string.toTitleCase('one_two_three', '_-.') => 'One_Two_Three' + * goog.string.toTitleCase('one-two-three', '_-.') => 'One-Two-Three' + * goog.string.toTitleCase('one...two...three', '_-.') => 'One...Two...Three' + * goog.string.toTitleCase('one. two. three', '_-.') => 'One. two. three' + * goog.string.toTitleCase('one-two.three', '_-.') => 'One-Two.Three' + * + * @param {string} str String value in camelCase form. + * @param {string=} opt_delimiters Custom delimiter character set used to + * distinguish words in the string value. Each character represents a + * single delimiter. When provided, default whitespace delimiter is + * overridden and must be explicitly included if needed. + * @return {string} String value in TitleCase form. + */ +goog.string.toTitleCase = function(str, opt_delimiters) { + var delimiters = goog.isString(opt_delimiters) ? + goog.string.regExpEscape(opt_delimiters) : '\\s'; + + // For IE8, we need to prevent using an empty character set. Otherwise, + // incorrect matching will occur. + delimiters = delimiters ? '|[' + delimiters + ']+' : ''; + + var regexp = new RegExp('(^' + delimiters + ')([a-z])', 'g'); + return str.replace(regexp, function(all, p1, p2) { + return p1 + p2.toUpperCase(); + }); +}; + + +/** + * Parse a string in decimal or hexidecimal ('0xFFFF') form. + * + * To parse a particular radix, please use parseInt(string, radix) directly. See + * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/parseInt + * + * This is a wrapper for the built-in parseInt function that will only parse + * numbers as base 10 or base 16. Some JS implementations assume strings + * starting with "0" are intended to be octal. ES3 allowed but discouraged + * this behavior. ES5 forbids it. This function emulates the ES5 behavior. + * + * For more information, see Mozilla JS Reference: http://goo.gl/8RiFj + * + * @param {string|number|null|undefined} value The value to be parsed. + * @return {number} The number, parsed. If the string failed to parse, this + * will be NaN. + */ +goog.string.parseInt = function(value) { + // Force finite numbers to strings. + if (isFinite(value)) { + value = String(value); + } + + if (goog.isString(value)) { + // If the string starts with '0x' or '-0x', parse as hex. + return /^\s*-?0x/i.test(value) ? + parseInt(value, 16) : parseInt(value, 10); + } + + return NaN; +}; + + +/** + * Splits a string on a separator a limited number of times. + * + * This implementation is more similar to Python or Java, where the limit + * parameter specifies the maximum number of splits rather than truncating + * the number of results. + * + * See http://docs.python.org/2/library/stdtypes.html#str.split + * See JavaDoc: http://goo.gl/F2AsY + * See Mozilla reference: http://goo.gl/dZdZs + * + * @param {string} str String to split. + * @param {string} separator The separator. + * @param {number} limit The limit to the number of splits. The resulting array + * will have a maximum length of limit+1. Negative numbers are the same + * as zero. + * @return {!Array.<string>} The string, split. + */ + +goog.string.splitLimit = function(str, separator, limit) { + var parts = str.split(separator); + var returnVal = []; + + // Only continue doing this while we haven't hit the limit and we have + // parts left. + while (limit > 0 && parts.length) { + returnVal.push(parts.shift()); + limit--; + } + + // If there are remaining parts, append them to the end. + if (parts.length) { + returnVal.push(parts.join(separator)); + } + + return returnVal; +}; + diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/collection.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/collection.js new file mode 100644 index 0000000..86d008d --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/collection.js @@ -0,0 +1,56 @@ +// Copyright 2011 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Defines the collection interface. + * + * @author nnaze@google.com (Nathan Naze) + */ + +goog.provide('goog.structs.Collection'); + + + +/** + * An interface for a collection of values. + * @interface + * @template T + */ +goog.structs.Collection = function() {}; + + +/** + * @param {T} value Value to add to the collection. + */ +goog.structs.Collection.prototype.add; + + +/** + * @param {T} value Value to remove from the collection. + */ +goog.structs.Collection.prototype.remove; + + +/** + * @param {T} value Value to find in the collection. + * @return {boolean} Whether the collection contains the specified value. + */ +goog.structs.Collection.prototype.contains; + + +/** + * @return {number} The number of values stored in the collection. + */ +goog.structs.Collection.prototype.getCount; + diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/map.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/map.js new file mode 100644 index 0000000..6db80fb --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/map.js @@ -0,0 +1,461 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Datastructure: Hash Map. + * + * @author arv@google.com (Erik Arvidsson) + * @author jonp@google.com (Jon Perlow) Optimized for IE6 + * + * This file contains an implementation of a Map structure. It implements a lot + * of the methods used in goog.structs so those functions work on hashes. This + * is best suited for complex key types. For simple keys such as numbers and + * strings, and where special names like __proto__ are not a concern, consider + * using the lighter-weight utilities in goog.object. + */ + + +goog.provide('goog.structs.Map'); + +goog.require('goog.iter.Iterator'); +goog.require('goog.iter.StopIteration'); +goog.require('goog.object'); + + + +/** + * Class for Hash Map datastructure. + * @param {*=} opt_map Map or Object to initialize the map with. + * @param {...*} var_args If 2 or more arguments are present then they + * will be used as key-value pairs. + * @constructor + * @template K, V + */ +goog.structs.Map = function(opt_map, var_args) { + + /** + * Underlying JS object used to implement the map. + * @private {!Object} + */ + this.map_ = {}; + + /** + * An array of keys. This is necessary for two reasons: + * 1. Iterating the keys using for (var key in this.map_) allocates an + * object for every key in IE which is really bad for IE6 GC perf. + * 2. Without a side data structure, we would need to escape all the keys + * as that would be the only way we could tell during iteration if the + * key was an internal key or a property of the object. + * + * This array can contain deleted keys so it's necessary to check the map + * as well to see if the key is still in the map (this doesn't require a + * memory allocation in IE). + * @private {!Array.<string>} + */ + this.keys_ = []; + + /** + * The number of key value pairs in the map. + * @private {number} + */ + this.count_ = 0; + + /** + * Version used to detect changes while iterating. + * @private {number} + */ + this.version_ = 0; + + var argLength = arguments.length; + + if (argLength > 1) { + if (argLength % 2) { + throw Error('Uneven number of arguments'); + } + for (var i = 0; i < argLength; i += 2) { + this.set(arguments[i], arguments[i + 1]); + } + } else if (opt_map) { + this.addAll(/** @type {Object} */ (opt_map)); + } +}; + + +/** + * @return {number} The number of key-value pairs in the map. + */ +goog.structs.Map.prototype.getCount = function() { + return this.count_; +}; + + +/** + * Returns the values of the map. + * @return {!Array.<V>} The values in the map. + */ +goog.structs.Map.prototype.getValues = function() { + this.cleanupKeysArray_(); + + var rv = []; + for (var i = 0; i < this.keys_.length; i++) { + var key = this.keys_[i]; + rv.push(this.map_[key]); + } + return rv; +}; + + +/** + * Returns the keys of the map. + * @return {!Array.<string>} Array of string values. + */ +goog.structs.Map.prototype.getKeys = function() { + this.cleanupKeysArray_(); + return /** @type {!Array.<string>} */ (this.keys_.concat()); +}; + + +/** + * Whether the map contains the given key. + * @param {*} key The key to check for. + * @return {boolean} Whether the map contains the key. + */ +goog.structs.Map.prototype.containsKey = function(key) { + return goog.structs.Map.hasKey_(this.map_, key); +}; + + +/** + * Whether the map contains the given value. This is O(n). + * @param {V} val The value to check for. + * @return {boolean} Whether the map contains the value. + */ +goog.structs.Map.prototype.containsValue = function(val) { + for (var i = 0; i < this.keys_.length; i++) { + var key = this.keys_[i]; + if (goog.structs.Map.hasKey_(this.map_, key) && this.map_[key] == val) { + return true; + } + } + return false; +}; + + +/** + * Whether this map is equal to the argument map. + * @param {goog.structs.Map} otherMap The map against which to test equality. + * @param {function(V, V): boolean=} opt_equalityFn Optional equality function + * to test equality of values. If not specified, this will test whether + * the values contained in each map are identical objects. + * @return {boolean} Whether the maps are equal. + */ +goog.structs.Map.prototype.equals = function(otherMap, opt_equalityFn) { + if (this === otherMap) { + return true; + } + + if (this.count_ != otherMap.getCount()) { + return false; + } + + var equalityFn = opt_equalityFn || goog.structs.Map.defaultEquals; + + this.cleanupKeysArray_(); + for (var key, i = 0; key = this.keys_[i]; i++) { + if (!equalityFn(this.get(key), otherMap.get(key))) { + return false; + } + } + + return true; +}; + + +/** + * Default equality test for values. + * @param {*} a The first value. + * @param {*} b The second value. + * @return {boolean} Whether a and b reference the same object. + */ +goog.structs.Map.defaultEquals = function(a, b) { + return a === b; +}; + + +/** + * @return {boolean} Whether the map is empty. + */ +goog.structs.Map.prototype.isEmpty = function() { + return this.count_ == 0; +}; + + +/** + * Removes all key-value pairs from the map. + */ +goog.structs.Map.prototype.clear = function() { + this.map_ = {}; + this.keys_.length = 0; + this.count_ = 0; + this.version_ = 0; +}; + + +/** + * Removes a key-value pair based on the key. This is O(logN) amortized due to + * updating the keys array whenever the count becomes half the size of the keys + * in the keys array. + * @param {*} key The key to remove. + * @return {boolean} Whether object was removed. + */ +goog.structs.Map.prototype.remove = function(key) { + if (goog.structs.Map.hasKey_(this.map_, key)) { + delete this.map_[key]; + this.count_--; + this.version_++; + + // clean up the keys array if the threshhold is hit + if (this.keys_.length > 2 * this.count_) { + this.cleanupKeysArray_(); + } + + return true; + } + return false; +}; + + +/** + * Cleans up the temp keys array by removing entries that are no longer in the + * map. + * @private + */ +goog.structs.Map.prototype.cleanupKeysArray_ = function() { + if (this.count_ != this.keys_.length) { + // First remove keys that are no longer in the map. + var srcIndex = 0; + var destIndex = 0; + while (srcIndex < this.keys_.length) { + var key = this.keys_[srcIndex]; + if (goog.structs.Map.hasKey_(this.map_, key)) { + this.keys_[destIndex++] = key; + } + srcIndex++; + } + this.keys_.length = destIndex; + } + + if (this.count_ != this.keys_.length) { + // If the count still isn't correct, that means we have duplicates. This can + // happen when the same key is added and removed multiple times. Now we have + // to allocate one extra Object to remove the duplicates. This could have + // been done in the first pass, but in the common case, we can avoid + // allocating an extra object by only doing this when necessary. + var seen = {}; + var srcIndex = 0; + var destIndex = 0; + while (srcIndex < this.keys_.length) { + var key = this.keys_[srcIndex]; + if (!(goog.structs.Map.hasKey_(seen, key))) { + this.keys_[destIndex++] = key; + seen[key] = 1; + } + srcIndex++; + } + this.keys_.length = destIndex; + } +}; + + +/** + * Returns the value for the given key. If the key is not found and the default + * value is not given this will return {@code undefined}. + * @param {*} key The key to get the value for. + * @param {DEFAULT=} opt_val The value to return if no item is found for the + * given key, defaults to undefined. + * @return {V|DEFAULT} The value for the given key. + * @template DEFAULT + */ +goog.structs.Map.prototype.get = function(key, opt_val) { + if (goog.structs.Map.hasKey_(this.map_, key)) { + return this.map_[key]; + } + return opt_val; +}; + + +/** + * Adds a key-value pair to the map. + * @param {*} key The key. + * @param {V} value The value to add. + * @return {*} Some subclasses return a value. + */ +goog.structs.Map.prototype.set = function(key, value) { + if (!(goog.structs.Map.hasKey_(this.map_, key))) { + this.count_++; + this.keys_.push(key); + // Only change the version if we add a new key. + this.version_++; + } + this.map_[key] = value; +}; + + +/** + * Adds multiple key-value pairs from another goog.structs.Map or Object. + * @param {Object} map Object containing the data to add. + */ +goog.structs.Map.prototype.addAll = function(map) { + var keys, values; + if (map instanceof goog.structs.Map) { + keys = map.getKeys(); + values = map.getValues(); + } else { + keys = goog.object.getKeys(map); + values = goog.object.getValues(map); + } + // we could use goog.array.forEach here but I don't want to introduce that + // dependency just for this. + for (var i = 0; i < keys.length; i++) { + this.set(keys[i], values[i]); + } +}; + + +/** + * Calls the given function on each entry in the map. + * @param {function(this:T, V, K, goog.structs.Map.<K,V>)} f + * @param {T=} opt_obj The value of "this" inside f. + * @template T + */ +goog.structs.Map.prototype.forEach = function(f, opt_obj) { + var keys = this.getKeys(); + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + var value = this.get(key); + f.call(opt_obj, value, key, this); + } +}; + + +/** + * Clones a map and returns a new map. + * @return {!goog.structs.Map} A new map with the same key-value pairs. + */ +goog.structs.Map.prototype.clone = function() { + return new goog.structs.Map(this); +}; + + +/** + * Returns a new map in which all the keys and values are interchanged + * (keys become values and values become keys). If multiple keys map to the + * same value, the chosen transposed value is implementation-dependent. + * + * It acts very similarly to {goog.object.transpose(Object)}. + * + * @return {!goog.structs.Map} The transposed map. + */ +goog.structs.Map.prototype.transpose = function() { + var transposed = new goog.structs.Map(); + for (var i = 0; i < this.keys_.length; i++) { + var key = this.keys_[i]; + var value = this.map_[key]; + transposed.set(value, key); + } + + return transposed; +}; + + +/** + * @return {!Object} Object representation of the map. + */ +goog.structs.Map.prototype.toObject = function() { + this.cleanupKeysArray_(); + var obj = {}; + for (var i = 0; i < this.keys_.length; i++) { + var key = this.keys_[i]; + obj[key] = this.map_[key]; + } + return obj; +}; + + +/** + * Returns an iterator that iterates over the keys in the map. Removal of keys + * while iterating might have undesired side effects. + * @return {!goog.iter.Iterator} An iterator over the keys in the map. + */ +goog.structs.Map.prototype.getKeyIterator = function() { + return this.__iterator__(true); +}; + + +/** + * Returns an iterator that iterates over the values in the map. Removal of + * keys while iterating might have undesired side effects. + * @return {!goog.iter.Iterator} An iterator over the values in the map. + */ +goog.structs.Map.prototype.getValueIterator = function() { + return this.__iterator__(false); +}; + + +/** + * Returns an iterator that iterates over the values or the keys in the map. + * This throws an exception if the map was mutated since the iterator was + * created. + * @param {boolean=} opt_keys True to iterate over the keys. False to iterate + * over the values. The default value is false. + * @return {!goog.iter.Iterator} An iterator over the values or keys in the map. + */ +goog.structs.Map.prototype.__iterator__ = function(opt_keys) { + // Clean up keys to minimize the risk of iterating over dead keys. + this.cleanupKeysArray_(); + + var i = 0; + var keys = this.keys_; + var map = this.map_; + var version = this.version_; + var selfObj = this; + + var newIter = new goog.iter.Iterator; + newIter.next = function() { + while (true) { + if (version != selfObj.version_) { + throw Error('The map has changed since the iterator was created'); + } + if (i >= keys.length) { + throw goog.iter.StopIteration; + } + var key = keys[i++]; + return opt_keys ? key : map[key]; + } + }; + return newIter; +}; + + +/** + * Safe way to test for hasOwnProperty. It even allows testing for + * 'hasOwnProperty'. + * @param {Object} obj The object to test for presence of the given key. + * @param {*} key The key to check for. + * @return {boolean} Whether the object has the key. + * @private + */ +goog.structs.Map.hasKey_ = function(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/set.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/set.js new file mode 100644 index 0000000..8f68298 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/set.js @@ -0,0 +1,280 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Datastructure: Set. + * + * @author arv@google.com (Erik Arvidsson) + * @author pallosp@google.com (Peter Pallos) + * + * This class implements a set data structure. Adding and removing is O(1). It + * supports both object and primitive values. Be careful because you can add + * both 1 and new Number(1), because these are not the same. You can even add + * multiple new Number(1) because these are not equal. + */ + + +goog.provide('goog.structs.Set'); + +goog.require('goog.structs'); +goog.require('goog.structs.Collection'); +goog.require('goog.structs.Map'); + + + +/** + * A set that can contain both primitives and objects. Adding and removing + * elements is O(1). Primitives are treated as identical if they have the same + * type and convert to the same string. Objects are treated as identical only + * if they are references to the same object. WARNING: A goog.structs.Set can + * contain both 1 and (new Number(1)), because they are not the same. WARNING: + * Adding (new Number(1)) twice will yield two distinct elements, because they + * are two different objects. WARNING: Any object that is added to a + * goog.structs.Set will be modified! Because goog.getUid() is used to + * identify objects, every object in the set will be mutated. + * @param {Array.<T>|Object.<?,T>=} opt_values Initial values to start with. + * @constructor + * @implements {goog.structs.Collection.<T>} + * @final + * @template T + */ +goog.structs.Set = function(opt_values) { + this.map_ = new goog.structs.Map; + if (opt_values) { + this.addAll(opt_values); + } +}; + + +/** + * Obtains a unique key for an element of the set. Primitives will yield the + * same key if they have the same type and convert to the same string. Object + * references will yield the same key only if they refer to the same object. + * @param {*} val Object or primitive value to get a key for. + * @return {string} A unique key for this value/object. + * @private + */ +goog.structs.Set.getKey_ = function(val) { + var type = typeof val; + if (type == 'object' && val || type == 'function') { + return 'o' + goog.getUid(/** @type {Object} */ (val)); + } else { + return type.substr(0, 1) + val; + } +}; + + +/** + * @return {number} The number of elements in the set. + * @override + */ +goog.structs.Set.prototype.getCount = function() { + return this.map_.getCount(); +}; + + +/** + * Add a primitive or an object to the set. + * @param {T} element The primitive or object to add. + * @override + */ +goog.structs.Set.prototype.add = function(element) { + this.map_.set(goog.structs.Set.getKey_(element), element); +}; + + +/** + * Adds all the values in the given collection to this set. + * @param {Array.<T>|goog.structs.Collection.<T>|Object.<?,T>} col A collection + * containing the elements to add. + */ +goog.structs.Set.prototype.addAll = function(col) { + var values = goog.structs.getValues(col); + var l = values.length; + for (var i = 0; i < l; i++) { + this.add(values[i]); + } +}; + + +/** + * Removes all values in the given collection from this set. + * @param {Array.<T>|goog.structs.Collection.<T>|Object.<?,T>} col A collection + * containing the elements to remove. + */ +goog.structs.Set.prototype.removeAll = function(col) { + var values = goog.structs.getValues(col); + var l = values.length; + for (var i = 0; i < l; i++) { + this.remove(values[i]); + } +}; + + +/** + * Removes the given element from this set. + * @param {T} element The primitive or object to remove. + * @return {boolean} Whether the element was found and removed. + * @override + */ +goog.structs.Set.prototype.remove = function(element) { + return this.map_.remove(goog.structs.Set.getKey_(element)); +}; + + +/** + * Removes all elements from this set. + */ +goog.structs.Set.prototype.clear = function() { + this.map_.clear(); +}; + + +/** + * Tests whether this set is empty. + * @return {boolean} True if there are no elements in this set. + */ +goog.structs.Set.prototype.isEmpty = function() { + return this.map_.isEmpty(); +}; + + +/** + * Tests whether this set contains the given element. + * @param {T} element The primitive or object to test for. + * @return {boolean} True if this set contains the given element. + * @override + */ +goog.structs.Set.prototype.contains = function(element) { + return this.map_.containsKey(goog.structs.Set.getKey_(element)); +}; + + +/** + * Tests whether this set contains all the values in a given collection. + * Repeated elements in the collection are ignored, e.g. (new + * goog.structs.Set([1, 2])).containsAll([1, 1]) is True. + * @param {goog.structs.Collection.<T>|Object} col A collection-like object. + * @return {boolean} True if the set contains all elements. + */ +goog.structs.Set.prototype.containsAll = function(col) { + return goog.structs.every(col, this.contains, this); +}; + + +/** + * Finds all values that are present in both this set and the given collection. + * @param {Array.<S>|Object.<?,S>} col A collection. + * @return {!goog.structs.Set.<T|S>} A new set containing all the values + * (primitives or objects) present in both this set and the given + * collection. + * @template S + */ +goog.structs.Set.prototype.intersection = function(col) { + var result = new goog.structs.Set(); + + var values = goog.structs.getValues(col); + for (var i = 0; i < values.length; i++) { + var value = values[i]; + if (this.contains(value)) { + result.add(value); + } + } + + return result; +}; + + +/** + * Finds all values that are present in this set and not in the given + * collection. + * @param {Array.<T>|goog.structs.Collection.<T>|Object.<?,T>} col A collection. + * @return {!goog.structs.Set} A new set containing all the values + * (primitives or objects) present in this set but not in the given + * collection. + */ +goog.structs.Set.prototype.difference = function(col) { + var result = this.clone(); + result.removeAll(col); + return result; +}; + + +/** + * Returns an array containing all the elements in this set. + * @return {!Array.<T>} An array containing all the elements in this set. + */ +goog.structs.Set.prototype.getValues = function() { + return this.map_.getValues(); +}; + + +/** + * Creates a shallow clone of this set. + * @return {!goog.structs.Set.<T>} A new set containing all the same elements as + * this set. + */ +goog.structs.Set.prototype.clone = function() { + return new goog.structs.Set(this); +}; + + +/** + * Tests whether the given collection consists of the same elements as this set, + * regardless of order, without repetition. Primitives are treated as equal if + * they have the same type and convert to the same string; objects are treated + * as equal if they are references to the same object. This operation is O(n). + * @param {goog.structs.Collection.<T>|Object} col A collection. + * @return {boolean} True if the given collection consists of the same elements + * as this set, regardless of order, without repetition. + */ +goog.structs.Set.prototype.equals = function(col) { + return this.getCount() == goog.structs.getCount(col) && this.isSubsetOf(col); +}; + + +/** + * Tests whether the given collection contains all the elements in this set. + * Primitives are treated as equal if they have the same type and convert to the + * same string; objects are treated as equal if they are references to the same + * object. This operation is O(n). + * @param {goog.structs.Collection.<T>|Object} col A collection. + * @return {boolean} True if this set is a subset of the given collection. + */ +goog.structs.Set.prototype.isSubsetOf = function(col) { + var colCount = goog.structs.getCount(col); + if (this.getCount() > colCount) { + return false; + } + // TODO(user) Find the minimal collection size where the conversion makes + // the contains() method faster. + if (!(col instanceof goog.structs.Set) && colCount > 5) { + // Convert to a goog.structs.Set so that goog.structs.contains runs in + // O(1) time instead of O(n) time. + col = new goog.structs.Set(col); + } + return goog.structs.every(this, function(value) { + return goog.structs.contains(col, value); + }); +}; + + +/** + * Returns an iterator that iterates over the elements in this set. + * @param {boolean=} opt_keys This argument is ignored. + * @return {!goog.iter.Iterator} An iterator over the elements in this set. + */ +goog.structs.Set.prototype.__iterator__ = function(opt_keys) { + return this.map_.__iterator__(false); +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/structs.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/structs.js new file mode 100644 index 0000000..edf9359 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/structs/structs.js @@ -0,0 +1,354 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Generics method for collection-like classes and objects. + * + * @author arv@google.com (Erik Arvidsson) + * + * This file contains functions to work with collections. It supports using + * Map, Set, Array and Object and other classes that implement collection-like + * methods. + */ + + +goog.provide('goog.structs'); + +goog.require('goog.array'); +goog.require('goog.object'); + + +// We treat an object as a dictionary if it has getKeys or it is an object that +// isn't arrayLike. + + +/** + * Returns the number of values in the collection-like object. + * @param {Object} col The collection-like object. + * @return {number} The number of values in the collection-like object. + */ +goog.structs.getCount = function(col) { + if (typeof col.getCount == 'function') { + return col.getCount(); + } + if (goog.isArrayLike(col) || goog.isString(col)) { + return col.length; + } + return goog.object.getCount(col); +}; + + +/** + * Returns the values of the collection-like object. + * @param {Object} col The collection-like object. + * @return {!Array} The values in the collection-like object. + */ +goog.structs.getValues = function(col) { + if (typeof col.getValues == 'function') { + return col.getValues(); + } + if (goog.isString(col)) { + return col.split(''); + } + if (goog.isArrayLike(col)) { + var rv = []; + var l = col.length; + for (var i = 0; i < l; i++) { + rv.push(col[i]); + } + return rv; + } + return goog.object.getValues(col); +}; + + +/** + * Returns the keys of the collection. Some collections have no notion of + * keys/indexes and this function will return undefined in those cases. + * @param {Object} col The collection-like object. + * @return {!Array|undefined} The keys in the collection. + */ +goog.structs.getKeys = function(col) { + if (typeof col.getKeys == 'function') { + return col.getKeys(); + } + // if we have getValues but no getKeys we know this is a key-less collection + if (typeof col.getValues == 'function') { + return undefined; + } + if (goog.isArrayLike(col) || goog.isString(col)) { + var rv = []; + var l = col.length; + for (var i = 0; i < l; i++) { + rv.push(i); + } + return rv; + } + + return goog.object.getKeys(col); +}; + + +/** + * Whether the collection contains the given value. This is O(n) and uses + * equals (==) to test the existence. + * @param {Object} col The collection-like object. + * @param {*} val The value to check for. + * @return {boolean} True if the map contains the value. + */ +goog.structs.contains = function(col, val) { + if (typeof col.contains == 'function') { + return col.contains(val); + } + if (typeof col.containsValue == 'function') { + return col.containsValue(val); + } + if (goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.contains(/** @type {Array} */ (col), val); + } + return goog.object.containsValue(col, val); +}; + + +/** + * Whether the collection is empty. + * @param {Object} col The collection-like object. + * @return {boolean} True if empty. + */ +goog.structs.isEmpty = function(col) { + if (typeof col.isEmpty == 'function') { + return col.isEmpty(); + } + + // We do not use goog.string.isEmpty because here we treat the string as + // collection and as such even whitespace matters + + if (goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.isEmpty(/** @type {Array} */ (col)); + } + return goog.object.isEmpty(col); +}; + + +/** + * Removes all the elements from the collection. + * @param {Object} col The collection-like object. + */ +goog.structs.clear = function(col) { + // NOTE(arv): This should not contain strings because strings are immutable + if (typeof col.clear == 'function') { + col.clear(); + } else if (goog.isArrayLike(col)) { + goog.array.clear(/** @type {goog.array.ArrayLike} */ (col)); + } else { + goog.object.clear(col); + } +}; + + +/** + * Calls a function for each value in a collection. The function takes + * three arguments; the value, the key and the collection. + * + * NOTE: This will be deprecated soon! Please use a more specific method if + * possible, e.g. goog.array.forEach, goog.object.forEach, etc. + * + * @param {S} col The collection-like object. + * @param {function(this:T,?,?,S):?} f The function to call for every value. + * This function takes + * 3 arguments (the value, the key or undefined if the collection has no + * notion of keys, and the collection) and the return value is irrelevant. + * @param {T=} opt_obj The object to be used as the value of 'this' + * within {@code f}. + * @template T,S + */ +goog.structs.forEach = function(col, f, opt_obj) { + if (typeof col.forEach == 'function') { + col.forEach(f, opt_obj); + } else if (goog.isArrayLike(col) || goog.isString(col)) { + goog.array.forEach(/** @type {Array} */ (col), f, opt_obj); + } else { + var keys = goog.structs.getKeys(col); + var values = goog.structs.getValues(col); + var l = values.length; + for (var i = 0; i < l; i++) { + f.call(opt_obj, values[i], keys && keys[i], col); + } + } +}; + + +/** + * Calls a function for every value in the collection. When a call returns true, + * adds the value to a new collection (Array is returned by default). + * + * @param {S} col The collection-like object. + * @param {function(this:T,?,?,S):boolean} f The function to call for every + * value. This function takes + * 3 arguments (the value, the key or undefined if the collection has no + * notion of keys, and the collection) and should return a Boolean. If the + * return value is true the value is added to the result collection. If it + * is false the value is not included. + * @param {T=} opt_obj The object to be used as the value of 'this' + * within {@code f}. + * @return {!Object|!Array} A new collection where the passed values are + * present. If col is a key-less collection an array is returned. If col + * has keys and values a plain old JS object is returned. + * @template T,S + */ +goog.structs.filter = function(col, f, opt_obj) { + if (typeof col.filter == 'function') { + return col.filter(f, opt_obj); + } + if (goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.filter(/** @type {!Array} */ (col), f, opt_obj); + } + + var rv; + var keys = goog.structs.getKeys(col); + var values = goog.structs.getValues(col); + var l = values.length; + if (keys) { + rv = {}; + for (var i = 0; i < l; i++) { + if (f.call(opt_obj, values[i], keys[i], col)) { + rv[keys[i]] = values[i]; + } + } + } else { + // We should not use goog.array.filter here since we want to make sure that + // the index is undefined as well as make sure that col is passed to the + // function. + rv = []; + for (var i = 0; i < l; i++) { + if (f.call(opt_obj, values[i], undefined, col)) { + rv.push(values[i]); + } + } + } + return rv; +}; + + +/** + * Calls a function for every value in the collection and adds the result into a + * new collection (defaults to creating a new Array). + * + * @param {S} col The collection-like object. + * @param {function(this:T,?,?,S):V} f The function to call for every value. + * This function takes 3 arguments (the value, the key or undefined if the + * collection has no notion of keys, and the collection) and should return + * something. The result will be used as the value in the new collection. + * @param {T=} opt_obj The object to be used as the value of 'this' + * within {@code f}. + * @return {!Object.<V>|!Array.<V>} A new collection with the new values. If + * col is a key-less collection an array is returned. If col has keys and + * values a plain old JS object is returned. + * @template T,S,V + */ +goog.structs.map = function(col, f, opt_obj) { + if (typeof col.map == 'function') { + return col.map(f, opt_obj); + } + if (goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.map(/** @type {!Array} */ (col), f, opt_obj); + } + + var rv; + var keys = goog.structs.getKeys(col); + var values = goog.structs.getValues(col); + var l = values.length; + if (keys) { + rv = {}; + for (var i = 0; i < l; i++) { + rv[keys[i]] = f.call(opt_obj, values[i], keys[i], col); + } + } else { + // We should not use goog.array.map here since we want to make sure that + // the index is undefined as well as make sure that col is passed to the + // function. + rv = []; + for (var i = 0; i < l; i++) { + rv[i] = f.call(opt_obj, values[i], undefined, col); + } + } + return rv; +}; + + +/** + * Calls f for each value in a collection. If any call returns true this returns + * true (without checking the rest). If all returns false this returns false. + * + * @param {S} col The collection-like object. + * @param {function(this:T,?,?,S):boolean} f The function to call for every + * value. This function takes 3 arguments (the value, the key or undefined + * if the collection has no notion of keys, and the collection) and should + * return a boolean. + * @param {T=} opt_obj The object to be used as the value of 'this' + * within {@code f}. + * @return {boolean} True if any value passes the test. + * @template T,S + */ +goog.structs.some = function(col, f, opt_obj) { + if (typeof col.some == 'function') { + return col.some(f, opt_obj); + } + if (goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.some(/** @type {!Array} */ (col), f, opt_obj); + } + var keys = goog.structs.getKeys(col); + var values = goog.structs.getValues(col); + var l = values.length; + for (var i = 0; i < l; i++) { + if (f.call(opt_obj, values[i], keys && keys[i], col)) { + return true; + } + } + return false; +}; + + +/** + * Calls f for each value in a collection. If all calls return true this return + * true this returns true. If any returns false this returns false at this point + * and does not continue to check the remaining values. + * + * @param {S} col The collection-like object. + * @param {function(this:T,?,?,S):boolean} f The function to call for every + * value. This function takes 3 arguments (the value, the key or + * undefined if the collection has no notion of keys, and the collection) + * and should return a boolean. + * @param {T=} opt_obj The object to be used as the value of 'this' + * within {@code f}. + * @return {boolean} True if all key-value pairs pass the test. + * @template T,S + */ +goog.structs.every = function(col, f, opt_obj) { + if (typeof col.every == 'function') { + return col.every(f, opt_obj); + } + if (goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.every(/** @type {!Array} */ (col), f, opt_obj); + } + var keys = goog.structs.getKeys(col); + var values = goog.structs.getValues(col); + var l = values.length; + for (var i = 0; i < l; i++) { + if (!f.call(opt_obj, values[i], keys && keys[i], col)) { + return false; + } + } + return true; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/style/bidi.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/style/bidi.js new file mode 100644 index 0000000..2d5c7c5 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/style/bidi.js @@ -0,0 +1,184 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Bidi utility functions. + * + */ + +goog.provide('goog.style.bidi'); + +goog.require('goog.dom'); +goog.require('goog.style'); +goog.require('goog.userAgent'); + + +/** + * Returns the normalized scrollLeft position for a scrolled element. + * @param {Element} element The scrolled element. + * @return {number} The number of pixels the element is scrolled. 0 indicates + * that the element is not scrolled at all (which, in general, is the + * left-most position in ltr and the right-most position in rtl). + */ +goog.style.bidi.getScrollLeft = function(element) { + var isRtl = goog.style.isRightToLeft(element); + if (isRtl && goog.userAgent.GECKO) { + // ScrollLeft starts at 0 and then goes negative as the element is scrolled + // towards the left. + return -element.scrollLeft; + } else if (isRtl && + !(goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8'))) { + // ScrollLeft starts at the maximum positive value and decreases towards + // 0 as the element is scrolled towards the left. However, for overflow + // visible, there is no scrollLeft and the value always stays correctly at 0 + var overflowX = goog.style.getComputedOverflowX(element); + if (overflowX == 'visible') { + return element.scrollLeft; + } else { + return element.scrollWidth - element.clientWidth - element.scrollLeft; + } + } + // ScrollLeft behavior is identical in rtl and ltr, it starts at 0 and + // increases as the element is scrolled away from the start. + return element.scrollLeft; +}; + + +/** + * Returns the "offsetStart" of an element, analagous to offsetLeft but + * normalized for right-to-left environments and various browser + * inconsistencies. This value returned can always be passed to setScrollOffset + * to scroll to an element's left edge in a left-to-right offsetParent or + * right edge in a right-to-left offsetParent. + * + * For example, here offsetStart is 10px in an LTR environment and 5px in RTL: + * + * <pre> + * | xxxxxxxxxx | + * ^^^^^^^^^^ ^^^^ ^^^^^ + * 10px elem 5px + * </pre> + * + * If an element is positioned before the start of its offsetParent, the + * startOffset may be negative. This can be used with setScrollOffset to + * reliably scroll to an element: + * + * <pre> + * var scrollOffset = goog.style.bidi.getOffsetStart(element); + * goog.style.bidi.setScrollOffset(element.offsetParent, scrollOffset); + * </pre> + * + * @see setScrollOffset + * + * @param {Element} element The element for which we need to determine the + * offsetStart position. + * @return {number} The offsetStart for that element. + */ +goog.style.bidi.getOffsetStart = function(element) { + var offsetLeftForReal = element.offsetLeft; + + // The element might not have an offsetParent. + // For example, the node might not be attached to the DOM tree, + // and position:fixed children do not have an offset parent. + // Just try to do the best we can with what we have. + var bestParent = element.offsetParent; + + if (!bestParent && goog.style.getComputedPosition(element) == 'fixed') { + bestParent = goog.dom.getOwnerDocument(element).documentElement; + } + + // Just give up in this case. + if (!bestParent) { + return offsetLeftForReal; + } + + if (goog.userAgent.GECKO) { + // When calculating an element's offsetLeft, Firefox erroneously subtracts + // the border width from the actual distance. So we need to add it back. + var borderWidths = goog.style.getBorderBox(bestParent); + offsetLeftForReal += borderWidths.left; + } else if (goog.userAgent.isDocumentModeOrHigher(8) && + !goog.userAgent.isDocumentModeOrHigher(9)) { + // When calculating an element's offsetLeft, IE8/9-Standards Mode + // erroneously adds the border width to the actual distance. So we need to + // subtract it. + var borderWidths = goog.style.getBorderBox(bestParent); + offsetLeftForReal -= borderWidths.left; + } + + if (goog.style.isRightToLeft(bestParent)) { + // Right edge of the element relative to the left edge of its parent. + var elementRightOffset = offsetLeftForReal + element.offsetWidth; + + // Distance from the parent's right edge to the element's right edge. + return bestParent.clientWidth - elementRightOffset; + } + + return offsetLeftForReal; +}; + + +/** + * Sets the element's scrollLeft attribute so it is correctly scrolled by + * offsetStart pixels. This takes into account whether the element is RTL and + * the nuances of different browsers. To scroll to the "beginning" of an + * element use getOffsetStart to obtain the element's offsetStart value and then + * pass the value to setScrollOffset. + * @see getOffsetStart + * @param {Element} element The element to set scrollLeft on. + * @param {number} offsetStart The number of pixels to scroll the element. + * If this value is < 0, 0 is used. + */ +goog.style.bidi.setScrollOffset = function(element, offsetStart) { + offsetStart = Math.max(offsetStart, 0); + // In LTR and in "mirrored" browser RTL (such as IE), we set scrollLeft to + // the number of pixels to scroll. + // Otherwise, in RTL, we need to account for different browser behavior. + if (!goog.style.isRightToLeft(element)) { + element.scrollLeft = offsetStart; + } else if (goog.userAgent.GECKO) { + // Negative scroll-left positions in RTL. + element.scrollLeft = -offsetStart; + } else if (!(goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8'))) { + // Take the current scrollLeft value and move to the right by the + // offsetStart to get to the left edge of the element, and then by + // the clientWidth of the element to get to the right edge. + element.scrollLeft = + element.scrollWidth - offsetStart - element.clientWidth; + } else { + element.scrollLeft = offsetStart; + } +}; + + +/** + * Sets the element's left style attribute in LTR or right style attribute in + * RTL. Also clears the left attribute in RTL and the right attribute in LTR. + * @param {Element} elem The element to position. + * @param {number} left The left position in LTR; will be set as right in RTL. + * @param {?number} top The top position. If null only the left/right is set. + * @param {boolean} isRtl Whether we are in RTL mode. + */ +goog.style.bidi.setPosition = function(elem, left, top, isRtl) { + if (!goog.isNull(top)) { + elem.style.top = top + 'px'; + } + if (isRtl) { + elem.style.right = left + 'px'; + elem.style.left = ''; + } else { + elem.style.left = left + 'px'; + elem.style.right = ''; + } +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/style/style.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/style/style.js new file mode 100644 index 0000000..2b5baac --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/style/style.js @@ -0,0 +1,2105 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utilities for element styles. + * + * @see ../demos/inline_block_quirks.html + * @see ../demos/inline_block_standards.html + * @see ../demos/style_viewport.html + */ + +goog.provide('goog.style'); + + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.NodeType'); +goog.require('goog.dom.vendor'); +goog.require('goog.math.Box'); +goog.require('goog.math.Coordinate'); +goog.require('goog.math.Rect'); +goog.require('goog.math.Size'); +goog.require('goog.object'); +goog.require('goog.string'); +goog.require('goog.userAgent'); + + +/** + * @define {boolean} Whether we know at compile time that + * getBoundingClientRect() is present and bug-free on the browser. + */ +goog.define('goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS', false); + + +/** + * Sets a style value on an element. + * + * This function is not indended to patch issues in the browser's style + * handling, but to allow easy programmatic access to setting dash-separated + * style properties. An example is setting a batch of properties from a data + * object without overwriting old styles. When possible, use native APIs: + * elem.style.propertyKey = 'value' or (if obliterating old styles is fine) + * elem.style.cssText = 'property1: value1; property2: value2'. + * + * @param {Element} element The element to change. + * @param {string|Object} style If a string, a style name. If an object, a hash + * of style names to style values. + * @param {string|number|boolean=} opt_value If style was a string, then this + * should be the value. + */ +goog.style.setStyle = function(element, style, opt_value) { + if (goog.isString(style)) { + goog.style.setStyle_(element, opt_value, style); + } else { + goog.object.forEach(style, goog.partial(goog.style.setStyle_, element)); + } +}; + + +/** + * Sets a style value on an element, with parameters swapped to work with + * {@code goog.object.forEach()}. Prepends a vendor-specific prefix when + * necessary. + * @param {Element} element The element to change. + * @param {string|number|boolean|undefined} value Style value. + * @param {string} style Style name. + * @private + */ +goog.style.setStyle_ = function(element, value, style) { + var propertyName = goog.style.getVendorJsStyleName_(element, style); + + if (propertyName) { + element.style[propertyName] = value; + } +}; + + +/** + * Returns the style property name in camel-case. If it does not exist and a + * vendor-specific version of the property does exist, then return the vendor- + * specific property name instead. + * @param {Element} element The element to change. + * @param {string} style Style name. + * @return {string} Vendor-specific style. + * @private + */ +goog.style.getVendorJsStyleName_ = function(element, style) { + var camelStyle = goog.string.toCamelCase(style); + + if (element.style[camelStyle] === undefined) { + var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() + + goog.string.toTitleCase(camelStyle); + + if (element.style[prefixedStyle] !== undefined) { + return prefixedStyle; + } + } + + return camelStyle; +}; + + +/** + * Returns the style property name in CSS notation. If it does not exist and a + * vendor-specific version of the property does exist, then return the vendor- + * specific property name instead. + * @param {Element} element The element to change. + * @param {string} style Style name. + * @return {string} Vendor-specific style. + * @private + */ +goog.style.getVendorStyleName_ = function(element, style) { + var camelStyle = goog.string.toCamelCase(style); + + if (element.style[camelStyle] === undefined) { + var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() + + goog.string.toTitleCase(camelStyle); + + if (element.style[prefixedStyle] !== undefined) { + return goog.dom.vendor.getVendorPrefix() + '-' + style; + } + } + + return style; +}; + + +/** + * Retrieves an explicitly-set style value of a node. This returns '' if there + * isn't a style attribute on the element or if this style property has not been + * explicitly set in script. + * + * @param {Element} element Element to get style of. + * @param {string} property Property to get, css-style (if you have a camel-case + * property, use element.style[style]). + * @return {string} Style value. + */ +goog.style.getStyle = function(element, property) { + // element.style is '' for well-known properties which are unset. + // For for browser specific styles as 'filter' is undefined + // so we need to return '' explicitly to make it consistent across + // browsers. + var styleValue = element.style[goog.string.toCamelCase(property)]; + + // Using typeof here because of a bug in Safari 5.1, where this value + // was undefined, but === undefined returned false. + if (typeof(styleValue) !== 'undefined') { + return styleValue; + } + + return element.style[goog.style.getVendorJsStyleName_(element, property)] || + ''; +}; + + +/** + * Retrieves a computed style value of a node. It returns empty string if the + * value cannot be computed (which will be the case in Internet Explorer) or + * "none" if the property requested is an SVG one and it has not been + * explicitly set (firefox and webkit). + * + * @param {Element} element Element to get style of. + * @param {string} property Property to get (camel-case). + * @return {string} Style value. + */ +goog.style.getComputedStyle = function(element, property) { + var doc = goog.dom.getOwnerDocument(element); + if (doc.defaultView && doc.defaultView.getComputedStyle) { + var styles = doc.defaultView.getComputedStyle(element, null); + if (styles) { + // element.style[..] is undefined for browser specific styles + // as 'filter'. + return styles[property] || styles.getPropertyValue(property) || ''; + } + } + + return ''; +}; + + +/** + * Gets the cascaded style value of a node, or null if the value cannot be + * computed (only Internet Explorer can do this). + * + * @param {Element} element Element to get style of. + * @param {string} style Property to get (camel-case). + * @return {string} Style value. + */ +goog.style.getCascadedStyle = function(element, style) { + // TODO(nicksantos): This should be documented to return null. #fixTypes + return element.currentStyle ? element.currentStyle[style] : null; +}; + + +/** + * Cross-browser pseudo get computed style. It returns the computed style where + * available. If not available it tries the cascaded style value (IE + * currentStyle) and in worst case the inline style value. It shouldn't be + * called directly, see http://wiki/Main/ComputedStyleVsCascadedStyle for + * discussion. + * + * @param {Element} element Element to get style of. + * @param {string} style Property to get (must be camelCase, not css-style.). + * @return {string} Style value. + * @private + */ +goog.style.getStyle_ = function(element, style) { + return goog.style.getComputedStyle(element, style) || + goog.style.getCascadedStyle(element, style) || + (element.style && element.style[style]); +}; + + +/** + * Retrieves the computed value of the box-sizing CSS attribute. + * Browser support: http://caniuse.com/css3-boxsizing. + * @param {!Element} element The element whose box-sizing to get. + * @return {?string} 'content-box', 'border-box' or 'padding-box'. null if + * box-sizing is not supported (IE7 and below). + */ +goog.style.getComputedBoxSizing = function(element) { + return goog.style.getStyle_(element, 'boxSizing') || + goog.style.getStyle_(element, 'MozBoxSizing') || + goog.style.getStyle_(element, 'WebkitBoxSizing') || null; +}; + + +/** + * Retrieves the computed value of the position CSS attribute. + * @param {Element} element The element to get the position of. + * @return {string} Position value. + */ +goog.style.getComputedPosition = function(element) { + return goog.style.getStyle_(element, 'position'); +}; + + +/** + * Retrieves the computed background color string for a given element. The + * string returned is suitable for assigning to another element's + * background-color, but is not guaranteed to be in any particular string + * format. Accessing the color in a numeric form may not be possible in all + * browsers or with all input. + * + * If the background color for the element is defined as a hexadecimal value, + * the resulting string can be parsed by goog.color.parse in all supported + * browsers. + * + * Whether named colors like "red" or "lightblue" get translated into a + * format which can be parsed is browser dependent. Calling this function on + * transparent elements will return "transparent" in most browsers or + * "rgba(0, 0, 0, 0)" in WebKit. + * @param {Element} element The element to get the background color of. + * @return {string} The computed string value of the background color. + */ +goog.style.getBackgroundColor = function(element) { + return goog.style.getStyle_(element, 'backgroundColor'); +}; + + +/** + * Retrieves the computed value of the overflow-x CSS attribute. + * @param {Element} element The element to get the overflow-x of. + * @return {string} The computed string value of the overflow-x attribute. + */ +goog.style.getComputedOverflowX = function(element) { + return goog.style.getStyle_(element, 'overflowX'); +}; + + +/** + * Retrieves the computed value of the overflow-y CSS attribute. + * @param {Element} element The element to get the overflow-y of. + * @return {string} The computed string value of the overflow-y attribute. + */ +goog.style.getComputedOverflowY = function(element) { + return goog.style.getStyle_(element, 'overflowY'); +}; + + +/** + * Retrieves the computed value of the z-index CSS attribute. + * @param {Element} element The element to get the z-index of. + * @return {string|number} The computed value of the z-index attribute. + */ +goog.style.getComputedZIndex = function(element) { + return goog.style.getStyle_(element, 'zIndex'); +}; + + +/** + * Retrieves the computed value of the text-align CSS attribute. + * @param {Element} element The element to get the text-align of. + * @return {string} The computed string value of the text-align attribute. + */ +goog.style.getComputedTextAlign = function(element) { + return goog.style.getStyle_(element, 'textAlign'); +}; + + +/** + * Retrieves the computed value of the cursor CSS attribute. + * @param {Element} element The element to get the cursor of. + * @return {string} The computed string value of the cursor attribute. + */ +goog.style.getComputedCursor = function(element) { + return goog.style.getStyle_(element, 'cursor'); +}; + + +/** + * Retrieves the computed value of the CSS transform attribute. + * @param {Element} element The element to get the transform of. + * @return {string} The computed string representation of the transform matrix. + */ +goog.style.getComputedTransform = function(element) { + var property = goog.style.getVendorStyleName_(element, 'transform'); + return goog.style.getStyle_(element, property) || + goog.style.getStyle_(element, 'transform'); +}; + + +/** + * Sets the top/left values of an element. If no unit is specified in the + * argument then it will add px. The second argument is required if the first + * argument is a string or number and is ignored if the first argument + * is a coordinate. + * @param {Element} el Element to move. + * @param {string|number|goog.math.Coordinate} arg1 Left position or coordinate. + * @param {string|number=} opt_arg2 Top position. + */ +goog.style.setPosition = function(el, arg1, opt_arg2) { + var x, y; + var buggyGeckoSubPixelPos = goog.userAgent.GECKO && + (goog.userAgent.MAC || goog.userAgent.X11) && + goog.userAgent.isVersionOrHigher('1.9'); + + if (arg1 instanceof goog.math.Coordinate) { + x = arg1.x; + y = arg1.y; + } else { + x = arg1; + y = opt_arg2; + } + + // Round to the nearest pixel for buggy sub-pixel support. + el.style.left = goog.style.getPixelStyleValue_( + /** @type {number|string} */ (x), buggyGeckoSubPixelPos); + el.style.top = goog.style.getPixelStyleValue_( + /** @type {number|string} */ (y), buggyGeckoSubPixelPos); +}; + + +/** + * Gets the offsetLeft and offsetTop properties of an element and returns them + * in a Coordinate object + * @param {Element} element Element. + * @return {!goog.math.Coordinate} The position. + */ +goog.style.getPosition = function(element) { + return new goog.math.Coordinate(element.offsetLeft, element.offsetTop); +}; + + +/** + * Returns the viewport element for a particular document + * @param {Node=} opt_node DOM node (Document is OK) to get the viewport element + * of. + * @return {Element} document.documentElement or document.body. + */ +goog.style.getClientViewportElement = function(opt_node) { + var doc; + if (opt_node) { + doc = goog.dom.getOwnerDocument(opt_node); + } else { + doc = goog.dom.getDocument(); + } + + // In old IE versions the document.body represented the viewport + if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) && + !goog.dom.getDomHelper(doc).isCss1CompatMode()) { + return doc.body; + } + return doc.documentElement; +}; + + +/** + * Calculates the viewport coordinates relative to the page/document + * containing the node. The viewport may be the browser viewport for + * non-iframe document, or the iframe container for iframe'd document. + * @param {!Document} doc The document to use as the reference point. + * @return {!goog.math.Coordinate} The page offset of the viewport. + */ +goog.style.getViewportPageOffset = function(doc) { + var body = doc.body; + var documentElement = doc.documentElement; + var scrollLeft = body.scrollLeft || documentElement.scrollLeft; + var scrollTop = body.scrollTop || documentElement.scrollTop; + return new goog.math.Coordinate(scrollLeft, scrollTop); +}; + + +/** + * Gets the client rectangle of the DOM element. + * + * getBoundingClientRect is part of a new CSS object model draft (with a + * long-time presence in IE), replacing the error-prone parent offset + * computation and the now-deprecated Gecko getBoxObjectFor. + * + * This utility patches common browser bugs in getBoundingClientRect. It + * will fail if getBoundingClientRect is unsupported. + * + * If the element is not in the DOM, the result is undefined, and an error may + * be thrown depending on user agent. + * + * @param {!Element} el The element whose bounding rectangle is being queried. + * @return {Object} A native bounding rectangle with numerical left, top, + * right, and bottom. Reported by Firefox to be of object type ClientRect. + * @private + */ +goog.style.getBoundingClientRect_ = function(el) { + var rect; + try { + rect = el.getBoundingClientRect(); + } catch (e) { + // In IE < 9, calling getBoundingClientRect on an orphan element raises an + // "Unspecified Error". All other browsers return zeros. + return {'left': 0, 'top': 0, 'right': 0, 'bottom': 0}; + } + + // Patch the result in IE only, so that this function can be inlined if + // compiled for non-IE. + if (goog.userAgent.IE && el.ownerDocument.body) { + + // In IE, most of the time, 2 extra pixels are added to the top and left + // due to the implicit 2-pixel inset border. In IE6/7 quirks mode and + // IE6 standards mode, this border can be overridden by setting the + // document element's border to zero -- thus, we cannot rely on the + // offset always being 2 pixels. + + // In quirks mode, the offset can be determined by querying the body's + // clientLeft/clientTop, but in standards mode, it is found by querying + // the document element's clientLeft/clientTop. Since we already called + // getBoundingClientRect we have already forced a reflow, so it is not + // too expensive just to query them all. + + // See: http://msdn.microsoft.com/en-us/library/ms536433(VS.85).aspx + var doc = el.ownerDocument; + rect.left -= doc.documentElement.clientLeft + doc.body.clientLeft; + rect.top -= doc.documentElement.clientTop + doc.body.clientTop; + } + return /** @type {Object} */ (rect); +}; + + +/** + * Returns the first parent that could affect the position of a given element. + * @param {Element} element The element to get the offset parent for. + * @return {Element} The first offset parent or null if one cannot be found. + */ +goog.style.getOffsetParent = function(element) { + // element.offsetParent does the right thing in IE7 and below. In other + // browsers it only includes elements with position absolute, relative or + // fixed, not elements with overflow set to auto or scroll. + if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(8)) { + return element.offsetParent; + } + + var doc = goog.dom.getOwnerDocument(element); + var positionStyle = goog.style.getStyle_(element, 'position'); + var skipStatic = positionStyle == 'fixed' || positionStyle == 'absolute'; + for (var parent = element.parentNode; parent && parent != doc; + parent = parent.parentNode) { + positionStyle = + goog.style.getStyle_(/** @type {!Element} */ (parent), 'position'); + skipStatic = skipStatic && positionStyle == 'static' && + parent != doc.documentElement && parent != doc.body; + if (!skipStatic && (parent.scrollWidth > parent.clientWidth || + parent.scrollHeight > parent.clientHeight || + positionStyle == 'fixed' || + positionStyle == 'absolute' || + positionStyle == 'relative')) { + return /** @type {!Element} */ (parent); + } + } + return null; +}; + + +/** + * Calculates and returns the visible rectangle for a given element. Returns a + * box describing the visible portion of the nearest scrollable offset ancestor. + * Coordinates are given relative to the document. + * + * @param {Element} element Element to get the visible rect for. + * @return {goog.math.Box} Bounding elementBox describing the visible rect or + * null if scrollable ancestor isn't inside the visible viewport. + */ +goog.style.getVisibleRectForElement = function(element) { + var visibleRect = new goog.math.Box(0, Infinity, Infinity, 0); + var dom = goog.dom.getDomHelper(element); + var body = dom.getDocument().body; + var documentElement = dom.getDocument().documentElement; + var scrollEl = dom.getDocumentScrollElement(); + + // Determine the size of the visible rect by climbing the dom accounting for + // all scrollable containers. + for (var el = element; el = goog.style.getOffsetParent(el); ) { + // clientWidth is zero for inline block elements in IE. + // on WEBKIT, body element can have clientHeight = 0 and scrollHeight > 0 + if ((!goog.userAgent.IE || el.clientWidth != 0) && + (!goog.userAgent.WEBKIT || el.clientHeight != 0 || el != body) && + // body may have overflow set on it, yet we still get the entire + // viewport. In some browsers, el.offsetParent may be + // document.documentElement, so check for that too. + (el != body && el != documentElement && + goog.style.getStyle_(el, 'overflow') != 'visible')) { + var pos = goog.style.getPageOffset(el); + var client = goog.style.getClientLeftTop(el); + pos.x += client.x; + pos.y += client.y; + + visibleRect.top = Math.max(visibleRect.top, pos.y); + visibleRect.right = Math.min(visibleRect.right, + pos.x + el.clientWidth); + visibleRect.bottom = Math.min(visibleRect.bottom, + pos.y + el.clientHeight); + visibleRect.left = Math.max(visibleRect.left, pos.x); + } + } + + // Clip by window's viewport. + var scrollX = scrollEl.scrollLeft, scrollY = scrollEl.scrollTop; + visibleRect.left = Math.max(visibleRect.left, scrollX); + visibleRect.top = Math.max(visibleRect.top, scrollY); + var winSize = dom.getViewportSize(); + visibleRect.right = Math.min(visibleRect.right, scrollX + winSize.width); + visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + winSize.height); + return visibleRect.top >= 0 && visibleRect.left >= 0 && + visibleRect.bottom > visibleRect.top && + visibleRect.right > visibleRect.left ? + visibleRect : null; +}; + + +/** + * Calculate the scroll position of {@code container} with the minimum amount so + * that the content and the borders of the given {@code element} become visible. + * If the element is bigger than the container, its top left corner will be + * aligned as close to the container's top left corner as possible. + * + * @param {Element} element The element to make visible. + * @param {Element} container The container to scroll. + * @param {boolean=} opt_center Whether to center the element in the container. + * Defaults to false. + * @return {!goog.math.Coordinate} The new scroll position of the container, + * in form of goog.math.Coordinate(scrollLeft, scrollTop). + */ +goog.style.getContainerOffsetToScrollInto = + function(element, container, opt_center) { + // Absolute position of the element's border's top left corner. + var elementPos = goog.style.getPageOffset(element); + // Absolute position of the container's border's top left corner. + var containerPos = goog.style.getPageOffset(container); + var containerBorder = goog.style.getBorderBox(container); + // Relative pos. of the element's border box to the container's content box. + var relX = elementPos.x - containerPos.x - containerBorder.left; + var relY = elementPos.y - containerPos.y - containerBorder.top; + // How much the element can move in the container, i.e. the difference between + // the element's bottom-right-most and top-left-most position where it's + // fully visible. + var spaceX = container.clientWidth - element.offsetWidth; + var spaceY = container.clientHeight - element.offsetHeight; + + var scrollLeft = container.scrollLeft; + var scrollTop = container.scrollTop; + if (opt_center) { + // All browsers round non-integer scroll positions down. + scrollLeft += relX - spaceX / 2; + scrollTop += relY - spaceY / 2; + } else { + // This formula was designed to give the correct scroll values in the + // following cases: + // - element is higher than container (spaceY < 0) => scroll down by relY + // - element is not higher that container (spaceY >= 0): + // - it is above container (relY < 0) => scroll up by abs(relY) + // - it is below container (relY > spaceY) => scroll down by relY - spaceY + // - it is in the container => don't scroll + scrollLeft += Math.min(relX, Math.max(relX - spaceX, 0)); + scrollTop += Math.min(relY, Math.max(relY - spaceY, 0)); + } + return new goog.math.Coordinate(scrollLeft, scrollTop); +}; + + +/** + * Changes the scroll position of {@code container} with the minimum amount so + * that the content and the borders of the given {@code element} become visible. + * If the element is bigger than the container, its top left corner will be + * aligned as close to the container's top left corner as possible. + * + * @param {Element} element The element to make visible. + * @param {Element} container The container to scroll. + * @param {boolean=} opt_center Whether to center the element in the container. + * Defaults to false. + */ +goog.style.scrollIntoContainerView = function(element, container, opt_center) { + var offset = + goog.style.getContainerOffsetToScrollInto(element, container, opt_center); + container.scrollLeft = offset.x; + container.scrollTop = offset.y; +}; + + +/** + * Returns clientLeft (width of the left border and, if the directionality is + * right to left, the vertical scrollbar) and clientTop as a coordinate object. + * + * @param {Element} el Element to get clientLeft for. + * @return {!goog.math.Coordinate} Client left and top. + */ +goog.style.getClientLeftTop = function(el) { + // NOTE(eae): Gecko prior to 1.9 doesn't support clientTop/Left, see + // https://bugzilla.mozilla.org/show_bug.cgi?id=111207 + if (goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('1.9')) { + var left = parseFloat(goog.style.getComputedStyle(el, 'borderLeftWidth')); + if (goog.style.isRightToLeft(el)) { + var scrollbarWidth = el.offsetWidth - el.clientWidth - left - + parseFloat(goog.style.getComputedStyle(el, 'borderRightWidth')); + left += scrollbarWidth; + } + return new goog.math.Coordinate(left, + parseFloat(goog.style.getComputedStyle(el, 'borderTopWidth'))); + } + + return new goog.math.Coordinate(el.clientLeft, el.clientTop); +}; + + +/** + * Returns a Coordinate object relative to the top-left of the HTML document. + * Implemented as a single function to save having to do two recursive loops in + * opera and safari just to get both coordinates. If you just want one value do + * use goog.style.getPageOffsetLeft() and goog.style.getPageOffsetTop(), but + * note if you call both those methods the tree will be analysed twice. + * + * @param {Element} el Element to get the page offset for. + * @return {!goog.math.Coordinate} The page offset. + */ +goog.style.getPageOffset = function(el) { + var box, doc = goog.dom.getOwnerDocument(el); + var positionStyle = goog.style.getStyle_(el, 'position'); + // TODO(gboyer): Update the jsdoc in a way that doesn't break the universe. + goog.asserts.assertObject(el, 'Parameter is required'); + + // NOTE(eae): Gecko pre 1.9 normally use getBoxObjectFor to calculate the + // position. When invoked for an element with position absolute and a negative + // position though it can be off by one. Therefor the recursive implementation + // is used in those (relatively rare) cases. + var BUGGY_GECKO_BOX_OBJECT = + !goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS && + goog.userAgent.GECKO && doc.getBoxObjectFor && + !el.getBoundingClientRect && positionStyle == 'absolute' && + (box = doc.getBoxObjectFor(el)) && (box.screenX < 0 || box.screenY < 0); + + // NOTE(arv): If element is hidden (display none or disconnected or any the + // ancestors are hidden) we get (0,0) by default but we still do the + // accumulation of scroll position. + + // TODO(arv): Should we check if the node is disconnected and in that case + // return (0,0)? + + var pos = new goog.math.Coordinate(0, 0); + var viewportElement = goog.style.getClientViewportElement(doc); + if (el == viewportElement) { + // viewport is always at 0,0 as that defined the coordinate system for this + // function - this avoids special case checks in the code below + return pos; + } + + // IE, Gecko 1.9+, and most modern WebKit. + if (goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS || + el.getBoundingClientRect) { + box = goog.style.getBoundingClientRect_(el); + // Must add the scroll coordinates in to get the absolute page offset + // of element since getBoundingClientRect returns relative coordinates to + // the viewport. + var scrollCoord = goog.dom.getDomHelper(doc).getDocumentScroll(); + pos.x = box.left + scrollCoord.x; + pos.y = box.top + scrollCoord.y; + + // Gecko prior to 1.9. + } else if (doc.getBoxObjectFor && !BUGGY_GECKO_BOX_OBJECT) { + // Gecko ignores the scroll values for ancestors, up to 1.9. See: + // https://bugzilla.mozilla.org/show_bug.cgi?id=328881 and + // https://bugzilla.mozilla.org/show_bug.cgi?id=330619 + + box = doc.getBoxObjectFor(el); + // TODO(user): Fix the off-by-one error when window is scrolled down + // or right more than 1 pixel. The viewport offset does not move in lock + // step with the window scroll; it moves in increments of 2px and at + // somewhat random intervals. + var vpBox = doc.getBoxObjectFor(viewportElement); + pos.x = box.screenX - vpBox.screenX; + pos.y = box.screenY - vpBox.screenY; + + // Safari, Opera and Camino up to 1.0.4. + } else { + var parent = el; + do { + pos.x += parent.offsetLeft; + pos.y += parent.offsetTop; + // For safari/chrome, we need to add parent's clientLeft/Top as well. + if (parent != el) { + pos.x += parent.clientLeft || 0; + pos.y += parent.clientTop || 0; + } + // In Safari when hit a position fixed element the rest of the offsets + // are not correct. + if (goog.userAgent.WEBKIT && + goog.style.getComputedPosition(parent) == 'fixed') { + pos.x += doc.body.scrollLeft; + pos.y += doc.body.scrollTop; + break; + } + parent = parent.offsetParent; + } while (parent && parent != el); + + // Opera & (safari absolute) incorrectly account for body offsetTop. + if (goog.userAgent.OPERA || (goog.userAgent.WEBKIT && + positionStyle == 'absolute')) { + pos.y -= doc.body.offsetTop; + } + + for (parent = el; (parent = goog.style.getOffsetParent(parent)) && + parent != doc.body && parent != viewportElement; ) { + pos.x -= parent.scrollLeft; + // Workaround for a bug in Opera 9.2 (and earlier) where table rows may + // report an invalid scroll top value. The bug was fixed in Opera 9.5 + // however as that version supports getBoundingClientRect it won't + // trigger this code path. https://bugs.opera.com/show_bug.cgi?id=249965 + if (!goog.userAgent.OPERA || parent.tagName != 'TR') { + pos.y -= parent.scrollTop; + } + } + } + + return pos; +}; + + +/** + * Returns the left coordinate of an element relative to the HTML document + * @param {Element} el Elements. + * @return {number} The left coordinate. + */ +goog.style.getPageOffsetLeft = function(el) { + return goog.style.getPageOffset(el).x; +}; + + +/** + * Returns the top coordinate of an element relative to the HTML document + * @param {Element} el Elements. + * @return {number} The top coordinate. + */ +goog.style.getPageOffsetTop = function(el) { + return goog.style.getPageOffset(el).y; +}; + + +/** + * Returns a Coordinate object relative to the top-left of an HTML document + * in an ancestor frame of this element. Used for measuring the position of + * an element inside a frame relative to a containing frame. + * + * @param {Element} el Element to get the page offset for. + * @param {Window} relativeWin The window to measure relative to. If relativeWin + * is not in the ancestor frame chain of the element, we measure relative to + * the top-most window. + * @return {!goog.math.Coordinate} The page offset. + */ +goog.style.getFramedPageOffset = function(el, relativeWin) { + var position = new goog.math.Coordinate(0, 0); + + // Iterate up the ancestor frame chain, keeping track of the current window + // and the current element in that window. + var currentWin = goog.dom.getWindow(goog.dom.getOwnerDocument(el)); + var currentEl = el; + do { + // if we're at the top window, we want to get the page offset. + // if we're at an inner frame, we only want to get the window position + // so that we can determine the actual page offset in the context of + // the outer window. + var offset = currentWin == relativeWin ? + goog.style.getPageOffset(currentEl) : + goog.style.getClientPositionForElement_( + goog.asserts.assert(currentEl)); + + position.x += offset.x; + position.y += offset.y; + } while (currentWin && currentWin != relativeWin && + (currentEl = currentWin.frameElement) && + (currentWin = currentWin.parent)); + + return position; +}; + + +/** + * Translates the specified rect relative to origBase page, for newBase page. + * If origBase and newBase are the same, this function does nothing. + * + * @param {goog.math.Rect} rect The source rectangle relative to origBase page, + * and it will have the translated result. + * @param {goog.dom.DomHelper} origBase The DomHelper for the input rectangle. + * @param {goog.dom.DomHelper} newBase The DomHelper for the resultant + * coordinate. This must be a DOM for an ancestor frame of origBase + * or the same as origBase. + */ +goog.style.translateRectForAnotherFrame = function(rect, origBase, newBase) { + if (origBase.getDocument() != newBase.getDocument()) { + var body = origBase.getDocument().body; + var pos = goog.style.getFramedPageOffset(body, newBase.getWindow()); + + // Adjust Body's margin. + pos = goog.math.Coordinate.difference(pos, goog.style.getPageOffset(body)); + + if (goog.userAgent.IE && !origBase.isCss1CompatMode()) { + pos = goog.math.Coordinate.difference(pos, origBase.getDocumentScroll()); + } + + rect.left += pos.x; + rect.top += pos.y; + } +}; + + +/** + * Returns the position of an element relative to another element in the + * document. A relative to B + * @param {Element|Event|goog.events.Event} a Element or mouse event whose + * position we're calculating. + * @param {Element|Event|goog.events.Event} b Element or mouse event position + * is relative to. + * @return {!goog.math.Coordinate} The relative position. + */ +goog.style.getRelativePosition = function(a, b) { + var ap = goog.style.getClientPosition(a); + var bp = goog.style.getClientPosition(b); + return new goog.math.Coordinate(ap.x - bp.x, ap.y - bp.y); +}; + + +/** + * Returns the position of the event or the element's border box relative to + * the client viewport. + * @param {!Element} el Element whose position to get. + * @return {!goog.math.Coordinate} The position. + * @private + */ +goog.style.getClientPositionForElement_ = function(el) { + var pos; + if (goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS || + el.getBoundingClientRect) { + // IE, Gecko 1.9+, and most modern WebKit + var box = goog.style.getBoundingClientRect_(el); + pos = new goog.math.Coordinate(box.left, box.top); + } else { + var scrollCoord = goog.dom.getDomHelper(el).getDocumentScroll(); + var pageCoord = goog.style.getPageOffset(el); + pos = new goog.math.Coordinate( + pageCoord.x - scrollCoord.x, + pageCoord.y - scrollCoord.y); + } + + // Gecko below version 12 doesn't add CSS translation to the client position + // (using either getBoundingClientRect or getBoxOffsetFor) so we need to do + // so manually. + if (goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher(12)) { + return goog.math.Coordinate.sum(pos, goog.style.getCssTranslation(el)); + } else { + return pos; + } +}; + + +/** + * Returns the position of the event or the element's border box relative to + * the client viewport. + * @param {Element|Event|goog.events.Event} el Element or a mouse / touch event. + * @return {!goog.math.Coordinate} The position. + */ +goog.style.getClientPosition = function(el) { + goog.asserts.assert(el); + if (el.nodeType == goog.dom.NodeType.ELEMENT) { + return goog.style.getClientPositionForElement_( + /** @type {!Element} */ (el)); + } else { + var isAbstractedEvent = goog.isFunction(el.getBrowserEvent); + var be = /** @type {!goog.events.BrowserEvent} */ (el); + var targetEvent = el; + + if (el.targetTouches) { + targetEvent = el.targetTouches[0]; + } else if (isAbstractedEvent && be.getBrowserEvent().targetTouches) { + targetEvent = be.getBrowserEvent().targetTouches[0]; + } + + return new goog.math.Coordinate( + targetEvent.clientX, + targetEvent.clientY); + } +}; + + +/** + * Moves an element to the given coordinates relative to the client viewport. + * @param {Element} el Absolutely positioned element to set page offset for. + * It must be in the document. + * @param {number|goog.math.Coordinate} x Left position of the element's margin + * box or a coordinate object. + * @param {number=} opt_y Top position of the element's margin box. + */ +goog.style.setPageOffset = function(el, x, opt_y) { + // Get current pageoffset + var cur = goog.style.getPageOffset(el); + + if (x instanceof goog.math.Coordinate) { + opt_y = x.y; + x = x.x; + } + + // NOTE(arv): We cannot allow strings for x and y. We could but that would + // require us to manually transform between different units + + // Work out deltas + var dx = x - cur.x; + var dy = opt_y - cur.y; + + // Set position to current left/top + delta + goog.style.setPosition(el, el.offsetLeft + dx, el.offsetTop + dy); +}; + + +/** + * Sets the width/height values of an element. If an argument is numeric, + * or a goog.math.Size is passed, it is assumed to be pixels and will add + * 'px' after converting it to an integer in string form. (This just sets the + * CSS width and height properties so it might set content-box or border-box + * size depending on the box model the browser is using.) + * + * @param {Element} element Element to set the size of. + * @param {string|number|goog.math.Size} w Width of the element, or a + * size object. + * @param {string|number=} opt_h Height of the element. Required if w is not a + * size object. + */ +goog.style.setSize = function(element, w, opt_h) { + var h; + if (w instanceof goog.math.Size) { + h = w.height; + w = w.width; + } else { + if (opt_h == undefined) { + throw Error('missing height argument'); + } + h = opt_h; + } + + goog.style.setWidth(element, /** @type {string|number} */ (w)); + goog.style.setHeight(element, /** @type {string|number} */ (h)); +}; + + +/** + * Helper function to create a string to be set into a pixel-value style + * property of an element. Can round to the nearest integer value. + * + * @param {string|number} value The style value to be used. If a number, + * 'px' will be appended, otherwise the value will be applied directly. + * @param {boolean} round Whether to round the nearest integer (if property + * is a number). + * @return {string} The string value for the property. + * @private + */ +goog.style.getPixelStyleValue_ = function(value, round) { + if (typeof value == 'number') { + value = (round ? Math.round(value) : value) + 'px'; + } + + return value; +}; + + +/** + * Set the height of an element. Sets the element's style property. + * @param {Element} element Element to set the height of. + * @param {string|number} height The height value to set. If a number, 'px' + * will be appended, otherwise the value will be applied directly. + */ +goog.style.setHeight = function(element, height) { + element.style.height = goog.style.getPixelStyleValue_(height, true); +}; + + +/** + * Set the width of an element. Sets the element's style property. + * @param {Element} element Element to set the width of. + * @param {string|number} width The width value to set. If a number, 'px' + * will be appended, otherwise the value will be applied directly. + */ +goog.style.setWidth = function(element, width) { + element.style.width = goog.style.getPixelStyleValue_(width, true); +}; + + +/** + * Gets the height and width of an element, even if its display is none. + * + * Specifically, this returns the height and width of the border box, + * irrespective of the box model in effect. + * + * Note that this function does not take CSS transforms into account. Please see + * {@code goog.style.getTransformedSize}. + * @param {Element} element Element to get size of. + * @return {!goog.math.Size} Object with width/height properties. + */ +goog.style.getSize = function(element) { + return goog.style.evaluateWithTemporaryDisplay_( + goog.style.getSizeWithDisplay_, /** @type {!Element} */ (element)); +}; + + +/** + * Call {@code fn} on {@code element} such that {@code element}'s dimensions are + * accurate when it's passed to {@code fn}. + * @param {function(!Element): T} fn Function to call with {@code element} as + * an argument after temporarily changing {@code element}'s display such + * that its dimensions are accurate. + * @param {!Element} element Element (which may have display none) to use as + * argument to {@code fn}. + * @return {T} Value returned by calling {@code fn} with {@code element}. + * @template T + * @private + */ +goog.style.evaluateWithTemporaryDisplay_ = function(fn, element) { + if (goog.style.getStyle_(element, 'display') != 'none') { + return fn(element); + } + + var style = element.style; + var originalDisplay = style.display; + var originalVisibility = style.visibility; + var originalPosition = style.position; + + style.visibility = 'hidden'; + style.position = 'absolute'; + style.display = 'inline'; + + var retVal = fn(element); + + style.display = originalDisplay; + style.position = originalPosition; + style.visibility = originalVisibility; + + return retVal; +}; + + +/** + * Gets the height and width of an element when the display is not none. + * @param {Element} element Element to get size of. + * @return {!goog.math.Size} Object with width/height properties. + * @private + */ +goog.style.getSizeWithDisplay_ = function(element) { + var offsetWidth = element.offsetWidth; + var offsetHeight = element.offsetHeight; + var webkitOffsetsZero = + goog.userAgent.WEBKIT && !offsetWidth && !offsetHeight; + if ((!goog.isDef(offsetWidth) || webkitOffsetsZero) && + element.getBoundingClientRect) { + // Fall back to calling getBoundingClientRect when offsetWidth or + // offsetHeight are not defined, or when they are zero in WebKit browsers. + // This makes sure that we return for the correct size for SVG elements, but + // will still return 0 on Webkit prior to 534.8, see + // http://trac.webkit.org/changeset/67252. + var clientRect = goog.style.getBoundingClientRect_(element); + return new goog.math.Size(clientRect.right - clientRect.left, + clientRect.bottom - clientRect.top); + } + return new goog.math.Size(offsetWidth, offsetHeight); +}; + + +/** + * Gets the height and width of an element, post transform, even if its display + * is none. + * + * This is like {@code goog.style.getSize}, except: + * <ol> + * <li>Takes webkitTransforms such as rotate and scale into account. + * <li>Will return null if {@code element} doesn't respond to + * {@code getBoundingClientRect}. + * <li>Currently doesn't make sense on non-WebKit browsers which don't support + * webkitTransforms. + * </ol> + * @param {!Element} element Element to get size of. + * @return {goog.math.Size} Object with width/height properties. + */ +goog.style.getTransformedSize = function(element) { + if (!element.getBoundingClientRect) { + return null; + } + + var clientRect = goog.style.evaluateWithTemporaryDisplay_( + goog.style.getBoundingClientRect_, element); + return new goog.math.Size(clientRect.right - clientRect.left, + clientRect.bottom - clientRect.top); +}; + + +/** + * Returns a bounding rectangle for a given element in page space. + * @param {Element} element Element to get bounds of. Must not be display none. + * @return {!goog.math.Rect} Bounding rectangle for the element. + */ +goog.style.getBounds = function(element) { + var o = goog.style.getPageOffset(element); + var s = goog.style.getSize(element); + return new goog.math.Rect(o.x, o.y, s.width, s.height); +}; + + +/** + * Converts a CSS selector in the form style-property to styleProperty. + * @param {*} selector CSS Selector. + * @return {string} Camel case selector. + * @deprecated Use goog.string.toCamelCase instead. + */ +goog.style.toCamelCase = function(selector) { + return goog.string.toCamelCase(String(selector)); +}; + + +/** + * Converts a CSS selector in the form styleProperty to style-property. + * @param {string} selector Camel case selector. + * @return {string} Selector cased. + * @deprecated Use goog.string.toSelectorCase instead. + */ +goog.style.toSelectorCase = function(selector) { + return goog.string.toSelectorCase(selector); +}; + + +/** + * Gets the opacity of a node (x-browser). This gets the inline style opacity + * of the node, and does not take into account the cascaded or the computed + * style for this node. + * @param {Element} el Element whose opacity has to be found. + * @return {number|string} Opacity between 0 and 1 or an empty string {@code ''} + * if the opacity is not set. + */ +goog.style.getOpacity = function(el) { + var style = el.style; + var result = ''; + if ('opacity' in style) { + result = style.opacity; + } else if ('MozOpacity' in style) { + result = style.MozOpacity; + } else if ('filter' in style) { + var match = style.filter.match(/alpha\(opacity=([\d.]+)\)/); + if (match) { + result = String(match[1] / 100); + } + } + return result == '' ? result : Number(result); +}; + + +/** + * Sets the opacity of a node (x-browser). + * @param {Element} el Elements whose opacity has to be set. + * @param {number|string} alpha Opacity between 0 and 1 or an empty string + * {@code ''} to clear the opacity. + */ +goog.style.setOpacity = function(el, alpha) { + var style = el.style; + if ('opacity' in style) { + style.opacity = alpha; + } else if ('MozOpacity' in style) { + style.MozOpacity = alpha; + } else if ('filter' in style) { + // TODO(arv): Overwriting the filter might have undesired side effects. + if (alpha === '') { + style.filter = ''; + } else { + style.filter = 'alpha(opacity=' + alpha * 100 + ')'; + } + } +}; + + +/** + * Sets the background of an element to a transparent image in a browser- + * independent manner. + * + * This function does not support repeating backgrounds or alternate background + * positions to match the behavior of Internet Explorer. It also does not + * support sizingMethods other than crop since they cannot be replicated in + * browsers other than Internet Explorer. + * + * @param {Element} el The element to set background on. + * @param {string} src The image source URL. + */ +goog.style.setTransparentBackgroundImage = function(el, src) { + var style = el.style; + // It is safe to use the style.filter in IE only. In Safari 'filter' is in + // style object but access to style.filter causes it to throw an exception. + // Note: IE8 supports images with an alpha channel. + if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) { + // See TODO in setOpacity. + style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(' + + 'src="' + src + '", sizingMethod="crop")'; + } else { + // Set style properties individually instead of using background shorthand + // to prevent overwriting a pre-existing background color. + style.backgroundImage = 'url(' + src + ')'; + style.backgroundPosition = 'top left'; + style.backgroundRepeat = 'no-repeat'; + } +}; + + +/** + * Clears the background image of an element in a browser independent manner. + * @param {Element} el The element to clear background image for. + */ +goog.style.clearTransparentBackgroundImage = function(el) { + var style = el.style; + if ('filter' in style) { + // See TODO in setOpacity. + style.filter = ''; + } else { + // Set style properties individually instead of using background shorthand + // to prevent overwriting a pre-existing background color. + style.backgroundImage = 'none'; + } +}; + + +/** + * Shows or hides an element from the page. Hiding the element is done by + * setting the display property to "none", removing the element from the + * rendering hierarchy so it takes up no space. To show the element, the default + * inherited display property is restored (defined either in stylesheets or by + * the browser's default style rules.) + * + * Caveat 1: if the inherited display property for the element is set to "none" + * by the stylesheets, that is the property that will be restored by a call to + * showElement(), effectively toggling the display between "none" and "none". + * + * Caveat 2: if the element display style is set inline (by setting either + * element.style.display or a style attribute in the HTML), a call to + * showElement will clear that setting and defer to the inherited style in the + * stylesheet. + * @param {Element} el Element to show or hide. + * @param {*} display True to render the element in its default style, + * false to disable rendering the element. + * @deprecated Use goog.style.setElementShown instead. + */ +goog.style.showElement = function(el, display) { + goog.style.setElementShown(el, display); +}; + + +/** + * Shows or hides an element from the page. Hiding the element is done by + * setting the display property to "none", removing the element from the + * rendering hierarchy so it takes up no space. To show the element, the default + * inherited display property is restored (defined either in stylesheets or by + * the browser's default style rules). + * + * Caveat 1: if the inherited display property for the element is set to "none" + * by the stylesheets, that is the property that will be restored by a call to + * setElementShown(), effectively toggling the display between "none" and + * "none". + * + * Caveat 2: if the element display style is set inline (by setting either + * element.style.display or a style attribute in the HTML), a call to + * setElementShown will clear that setting and defer to the inherited style in + * the stylesheet. + * @param {Element} el Element to show or hide. + * @param {*} isShown True to render the element in its default style, + * false to disable rendering the element. + */ +goog.style.setElementShown = function(el, isShown) { + el.style.display = isShown ? '' : 'none'; +}; + + +/** + * Test whether the given element has been shown or hidden via a call to + * {@link #setElementShown}. + * + * Note this is strictly a companion method for a call + * to {@link #setElementShown} and the same caveats apply; in particular, this + * method does not guarantee that the return value will be consistent with + * whether or not the element is actually visible. + * + * @param {Element} el The element to test. + * @return {boolean} Whether the element has been shown. + * @see #setElementShown + */ +goog.style.isElementShown = function(el) { + return el.style.display != 'none'; +}; + + +/** + * Installs the styles string into the window that contains opt_element. If + * opt_element is null, the main window is used. + * @param {string} stylesString The style string to install. + * @param {Node=} opt_node Node whose parent document should have the + * styles installed. + * @return {Element|StyleSheet} The style element created. + */ +goog.style.installStyles = function(stylesString, opt_node) { + var dh = goog.dom.getDomHelper(opt_node); + var styleSheet = null; + + // IE < 11 requires createStyleSheet. Note that doc.createStyleSheet will be + // undefined as of IE 11. + var doc = dh.getDocument(); + if (goog.userAgent.IE && doc.createStyleSheet) { + styleSheet = doc.createStyleSheet(); + goog.style.setStyles(styleSheet, stylesString); + } else { + var head = dh.getElementsByTagNameAndClass('head')[0]; + + // In opera documents are not guaranteed to have a head element, thus we + // have to make sure one exists before using it. + if (!head) { + var body = dh.getElementsByTagNameAndClass('body')[0]; + head = dh.createDom('head'); + body.parentNode.insertBefore(head, body); + } + styleSheet = dh.createDom('style'); + // NOTE(user): Setting styles after the style element has been appended + // to the head results in a nasty Webkit bug in certain scenarios. Please + // refer to https://bugs.webkit.org/show_bug.cgi?id=26307 for additional + // details. + goog.style.setStyles(styleSheet, stylesString); + dh.appendChild(head, styleSheet); + } + return styleSheet; +}; + + +/** + * Removes the styles added by {@link #installStyles}. + * @param {Element|StyleSheet} styleSheet The value returned by + * {@link #installStyles}. + */ +goog.style.uninstallStyles = function(styleSheet) { + var node = styleSheet.ownerNode || styleSheet.owningElement || + /** @type {Element} */ (styleSheet); + goog.dom.removeNode(node); +}; + + +/** + * Sets the content of a style element. The style element can be any valid + * style element. This element will have its content completely replaced by + * the new stylesString. + * @param {Element|StyleSheet} element A stylesheet element as returned by + * installStyles. + * @param {string} stylesString The new content of the stylesheet. + */ +goog.style.setStyles = function(element, stylesString) { + if (goog.userAgent.IE && goog.isDef(element.cssText)) { + // Adding the selectors individually caused the browser to hang if the + // selector was invalid or there were CSS comments. Setting the cssText of + // the style node works fine and ignores CSS that IE doesn't understand. + // However IE >= 11 doesn't support cssText any more, so we make sure that + // cssText is a defined property and otherwise fall back to innerHTML. + element.cssText = stylesString; + } else { + element.innerHTML = stylesString; + } +}; + + +/** + * Sets 'white-space: pre-wrap' for a node (x-browser). + * + * There are as many ways of specifying pre-wrap as there are browsers. + * + * CSS3/IE8: white-space: pre-wrap; + * Mozilla: white-space: -moz-pre-wrap; + * Opera: white-space: -o-pre-wrap; + * IE6/7: white-space: pre; word-wrap: break-word; + * + * @param {Element} el Element to enable pre-wrap for. + */ +goog.style.setPreWrap = function(el) { + var style = el.style; + if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) { + style.whiteSpace = 'pre'; + style.wordWrap = 'break-word'; + } else if (goog.userAgent.GECKO) { + style.whiteSpace = '-moz-pre-wrap'; + } else { + style.whiteSpace = 'pre-wrap'; + } +}; + + +/** + * Sets 'display: inline-block' for an element (cross-browser). + * @param {Element} el Element to which the inline-block display style is to be + * applied. + * @see ../demos/inline_block_quirks.html + * @see ../demos/inline_block_standards.html + */ +goog.style.setInlineBlock = function(el) { + var style = el.style; + // Without position:relative, weirdness ensues. Just accept it and move on. + style.position = 'relative'; + + if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) { + // IE8 supports inline-block so fall through to the else + // Zoom:1 forces hasLayout, display:inline gives inline behavior. + style.zoom = '1'; + style.display = 'inline'; + } else if (goog.userAgent.GECKO) { + // Pre-Firefox 3, Gecko doesn't support inline-block, but -moz-inline-box + // is close enough. + style.display = goog.userAgent.isVersionOrHigher('1.9a') ? 'inline-block' : + '-moz-inline-box'; + } else { + // Opera, Webkit, and Safari seem to do OK with the standard inline-block + // style. + style.display = 'inline-block'; + } +}; + + +/** + * Returns true if the element is using right to left (rtl) direction. + * @param {Element} el The element to test. + * @return {boolean} True for right to left, false for left to right. + */ +goog.style.isRightToLeft = function(el) { + return 'rtl' == goog.style.getStyle_(el, 'direction'); +}; + + +/** + * The CSS style property corresponding to an element being + * unselectable on the current browser platform (null if none). + * Opera and IE instead use a DOM attribute 'unselectable'. + * @type {?string} + * @private + */ +goog.style.unselectableStyle_ = + goog.userAgent.GECKO ? 'MozUserSelect' : + goog.userAgent.WEBKIT ? 'WebkitUserSelect' : + null; + + +/** + * Returns true if the element is set to be unselectable, false otherwise. + * Note that on some platforms (e.g. Mozilla), even if an element isn't set + * to be unselectable, it will behave as such if any of its ancestors is + * unselectable. + * @param {Element} el Element to check. + * @return {boolean} Whether the element is set to be unselectable. + */ +goog.style.isUnselectable = function(el) { + if (goog.style.unselectableStyle_) { + return el.style[goog.style.unselectableStyle_].toLowerCase() == 'none'; + } else if (goog.userAgent.IE || goog.userAgent.OPERA) { + return el.getAttribute('unselectable') == 'on'; + } + return false; +}; + + +/** + * Makes the element and its descendants selectable or unselectable. Note + * that on some platforms (e.g. Mozilla), even if an element isn't set to + * be unselectable, it will behave as such if any of its ancestors is + * unselectable. + * @param {Element} el The element to alter. + * @param {boolean} unselectable Whether the element and its descendants + * should be made unselectable. + * @param {boolean=} opt_noRecurse Whether to only alter the element's own + * selectable state, and leave its descendants alone; defaults to false. + */ +goog.style.setUnselectable = function(el, unselectable, opt_noRecurse) { + // TODO(attila): Do we need all of TR_DomUtil.makeUnselectable() in Closure? + var descendants = !opt_noRecurse ? el.getElementsByTagName('*') : null; + var name = goog.style.unselectableStyle_; + if (name) { + // Add/remove the appropriate CSS style to/from the element and its + // descendants. + var value = unselectable ? 'none' : ''; + el.style[name] = value; + if (descendants) { + for (var i = 0, descendant; descendant = descendants[i]; i++) { + descendant.style[name] = value; + } + } + } else if (goog.userAgent.IE || goog.userAgent.OPERA) { + // Toggle the 'unselectable' attribute on the element and its descendants. + var value = unselectable ? 'on' : ''; + el.setAttribute('unselectable', value); + if (descendants) { + for (var i = 0, descendant; descendant = descendants[i]; i++) { + descendant.setAttribute('unselectable', value); + } + } + } +}; + + +/** + * Gets the border box size for an element. + * @param {Element} element The element to get the size for. + * @return {!goog.math.Size} The border box size. + */ +goog.style.getBorderBoxSize = function(element) { + return new goog.math.Size(element.offsetWidth, element.offsetHeight); +}; + + +/** + * Sets the border box size of an element. This is potentially expensive in IE + * if the document is CSS1Compat mode + * @param {Element} element The element to set the size on. + * @param {goog.math.Size} size The new size. + */ +goog.style.setBorderBoxSize = function(element, size) { + var doc = goog.dom.getOwnerDocument(element); + var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode(); + + if (goog.userAgent.IE && + (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) { + var style = element.style; + if (isCss1CompatMode) { + var paddingBox = goog.style.getPaddingBox(element); + var borderBox = goog.style.getBorderBox(element); + style.pixelWidth = size.width - borderBox.left - paddingBox.left - + paddingBox.right - borderBox.right; + style.pixelHeight = size.height - borderBox.top - paddingBox.top - + paddingBox.bottom - borderBox.bottom; + } else { + style.pixelWidth = size.width; + style.pixelHeight = size.height; + } + } else { + goog.style.setBoxSizingSize_(element, size, 'border-box'); + } +}; + + +/** + * Gets the content box size for an element. This is potentially expensive in + * all browsers. + * @param {Element} element The element to get the size for. + * @return {!goog.math.Size} The content box size. + */ +goog.style.getContentBoxSize = function(element) { + var doc = goog.dom.getOwnerDocument(element); + var ieCurrentStyle = goog.userAgent.IE && element.currentStyle; + if (ieCurrentStyle && + goog.dom.getDomHelper(doc).isCss1CompatMode() && + ieCurrentStyle.width != 'auto' && ieCurrentStyle.height != 'auto' && + !ieCurrentStyle.boxSizing) { + // If IE in CSS1Compat mode than just use the width and height. + // If we have a boxSizing then fall back on measuring the borders etc. + var width = goog.style.getIePixelValue_(element, ieCurrentStyle.width, + 'width', 'pixelWidth'); + var height = goog.style.getIePixelValue_(element, ieCurrentStyle.height, + 'height', 'pixelHeight'); + return new goog.math.Size(width, height); + } else { + var borderBoxSize = goog.style.getBorderBoxSize(element); + var paddingBox = goog.style.getPaddingBox(element); + var borderBox = goog.style.getBorderBox(element); + return new goog.math.Size(borderBoxSize.width - + borderBox.left - paddingBox.left - + paddingBox.right - borderBox.right, + borderBoxSize.height - + borderBox.top - paddingBox.top - + paddingBox.bottom - borderBox.bottom); + } +}; + + +/** + * Sets the content box size of an element. This is potentially expensive in IE + * if the document is BackCompat mode. + * @param {Element} element The element to set the size on. + * @param {goog.math.Size} size The new size. + */ +goog.style.setContentBoxSize = function(element, size) { + var doc = goog.dom.getOwnerDocument(element); + var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode(); + if (goog.userAgent.IE && + (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) { + var style = element.style; + if (isCss1CompatMode) { + style.pixelWidth = size.width; + style.pixelHeight = size.height; + } else { + var paddingBox = goog.style.getPaddingBox(element); + var borderBox = goog.style.getBorderBox(element); + style.pixelWidth = size.width + borderBox.left + paddingBox.left + + paddingBox.right + borderBox.right; + style.pixelHeight = size.height + borderBox.top + paddingBox.top + + paddingBox.bottom + borderBox.bottom; + } + } else { + goog.style.setBoxSizingSize_(element, size, 'content-box'); + } +}; + + +/** + * Helper function that sets the box sizing as well as the width and height + * @param {Element} element The element to set the size on. + * @param {goog.math.Size} size The new size to set. + * @param {string} boxSizing The box-sizing value. + * @private + */ +goog.style.setBoxSizingSize_ = function(element, size, boxSizing) { + var style = element.style; + if (goog.userAgent.GECKO) { + style.MozBoxSizing = boxSizing; + } else if (goog.userAgent.WEBKIT) { + style.WebkitBoxSizing = boxSizing; + } else { + // Includes IE8 and Opera 9.50+ + style.boxSizing = boxSizing; + } + + // Setting this to a negative value will throw an exception on IE + // (and doesn't do anything different than setting it to 0). + style.width = Math.max(size.width, 0) + 'px'; + style.height = Math.max(size.height, 0) + 'px'; +}; + + +/** + * IE specific function that converts a non pixel unit to pixels. + * @param {Element} element The element to convert the value for. + * @param {string} value The current value as a string. The value must not be + * ''. + * @param {string} name The CSS property name to use for the converstion. This + * should be 'left', 'top', 'width' or 'height'. + * @param {string} pixelName The CSS pixel property name to use to get the + * value in pixels. + * @return {number} The value in pixels. + * @private + */ +goog.style.getIePixelValue_ = function(element, value, name, pixelName) { + // Try if we already have a pixel value. IE does not do half pixels so we + // only check if it matches a number followed by 'px'. + if (/^\d+px?$/.test(value)) { + return parseInt(value, 10); + } else { + var oldStyleValue = element.style[name]; + var oldRuntimeValue = element.runtimeStyle[name]; + // set runtime style to prevent changes + element.runtimeStyle[name] = element.currentStyle[name]; + element.style[name] = value; + var pixelValue = element.style[pixelName]; + // restore + element.style[name] = oldStyleValue; + element.runtimeStyle[name] = oldRuntimeValue; + return pixelValue; + } +}; + + +/** + * Helper function for getting the pixel padding or margin for IE. + * @param {Element} element The element to get the padding for. + * @param {string} propName The property name. + * @return {number} The pixel padding. + * @private + */ +goog.style.getIePixelDistance_ = function(element, propName) { + var value = goog.style.getCascadedStyle(element, propName); + return value ? + goog.style.getIePixelValue_(element, value, 'left', 'pixelLeft') : 0; +}; + + +/** + * Gets the computed paddings or margins (on all sides) in pixels. + * @param {Element} element The element to get the padding for. + * @param {string} stylePrefix Pass 'padding' to retrieve the padding box, + * or 'margin' to retrieve the margin box. + * @return {!goog.math.Box} The computed paddings or margins. + * @private + */ +goog.style.getBox_ = function(element, stylePrefix) { + if (goog.userAgent.IE) { + var left = goog.style.getIePixelDistance_(element, stylePrefix + 'Left'); + var right = goog.style.getIePixelDistance_(element, stylePrefix + 'Right'); + var top = goog.style.getIePixelDistance_(element, stylePrefix + 'Top'); + var bottom = goog.style.getIePixelDistance_( + element, stylePrefix + 'Bottom'); + return new goog.math.Box(top, right, bottom, left); + } else { + // On non-IE browsers, getComputedStyle is always non-null. + var left = /** @type {string} */ ( + goog.style.getComputedStyle(element, stylePrefix + 'Left')); + var right = /** @type {string} */ ( + goog.style.getComputedStyle(element, stylePrefix + 'Right')); + var top = /** @type {string} */ ( + goog.style.getComputedStyle(element, stylePrefix + 'Top')); + var bottom = /** @type {string} */ ( + goog.style.getComputedStyle(element, stylePrefix + 'Bottom')); + + // NOTE(arv): Gecko can return floating point numbers for the computed + // style values. + return new goog.math.Box(parseFloat(top), + parseFloat(right), + parseFloat(bottom), + parseFloat(left)); + } +}; + + +/** + * Gets the computed paddings (on all sides) in pixels. + * @param {Element} element The element to get the padding for. + * @return {!goog.math.Box} The computed paddings. + */ +goog.style.getPaddingBox = function(element) { + return goog.style.getBox_(element, 'padding'); +}; + + +/** + * Gets the computed margins (on all sides) in pixels. + * @param {Element} element The element to get the margins for. + * @return {!goog.math.Box} The computed margins. + */ +goog.style.getMarginBox = function(element) { + return goog.style.getBox_(element, 'margin'); +}; + + +/** + * A map used to map the border width keywords to a pixel width. + * @type {Object} + * @private + */ +goog.style.ieBorderWidthKeywords_ = { + 'thin': 2, + 'medium': 4, + 'thick': 6 +}; + + +/** + * Helper function for IE to get the pixel border. + * @param {Element} element The element to get the pixel border for. + * @param {string} prop The part of the property name. + * @return {number} The value in pixels. + * @private + */ +goog.style.getIePixelBorder_ = function(element, prop) { + if (goog.style.getCascadedStyle(element, prop + 'Style') == 'none') { + return 0; + } + var width = goog.style.getCascadedStyle(element, prop + 'Width'); + if (width in goog.style.ieBorderWidthKeywords_) { + return goog.style.ieBorderWidthKeywords_[width]; + } + return goog.style.getIePixelValue_(element, width, 'left', 'pixelLeft'); +}; + + +/** + * Gets the computed border widths (on all sides) in pixels + * @param {Element} element The element to get the border widths for. + * @return {!goog.math.Box} The computed border widths. + */ +goog.style.getBorderBox = function(element) { + if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) { + var left = goog.style.getIePixelBorder_(element, 'borderLeft'); + var right = goog.style.getIePixelBorder_(element, 'borderRight'); + var top = goog.style.getIePixelBorder_(element, 'borderTop'); + var bottom = goog.style.getIePixelBorder_(element, 'borderBottom'); + return new goog.math.Box(top, right, bottom, left); + } else { + // On non-IE browsers, getComputedStyle is always non-null. + var left = /** @type {string} */ ( + goog.style.getComputedStyle(element, 'borderLeftWidth')); + var right = /** @type {string} */ ( + goog.style.getComputedStyle(element, 'borderRightWidth')); + var top = /** @type {string} */ ( + goog.style.getComputedStyle(element, 'borderTopWidth')); + var bottom = /** @type {string} */ ( + goog.style.getComputedStyle(element, 'borderBottomWidth')); + + return new goog.math.Box(parseFloat(top), + parseFloat(right), + parseFloat(bottom), + parseFloat(left)); + } +}; + + +/** + * Returns the font face applied to a given node. Opera and IE should return + * the font actually displayed. Firefox returns the author's most-preferred + * font (whether the browser is capable of displaying it or not.) + * @param {Element} el The element whose font family is returned. + * @return {string} The font family applied to el. + */ +goog.style.getFontFamily = function(el) { + var doc = goog.dom.getOwnerDocument(el); + var font = ''; + // The moveToElementText method from the TextRange only works if the element + // is attached to the owner document. + if (doc.body.createTextRange && goog.dom.contains(doc, el)) { + var range = doc.body.createTextRange(); + range.moveToElementText(el); + /** @preserveTry */ + try { + font = range.queryCommandValue('FontName'); + } catch (e) { + // This is a workaround for a awkward exception. + // On some IE, there is an exception coming from it. + // The error description from this exception is: + // This window has already been registered as a drop target + // This is bogus description, likely due to a bug in ie. + font = ''; + } + } + if (!font) { + // Note if for some reason IE can't derive FontName with a TextRange, we + // fallback to using currentStyle + font = goog.style.getStyle_(el, 'fontFamily'); + } + + // Firefox returns the applied font-family string (author's list of + // preferred fonts.) We want to return the most-preferred font, in lieu of + // the *actually* applied font. + var fontsArray = font.split(','); + if (fontsArray.length > 1) font = fontsArray[0]; + + // Sanitize for x-browser consistency: + // Strip quotes because browsers aren't consistent with how they're + // applied; Opera always encloses, Firefox sometimes, and IE never. + return goog.string.stripQuotes(font, '"\''); +}; + + +/** + * Regular expression used for getLengthUnits. + * @type {RegExp} + * @private + */ +goog.style.lengthUnitRegex_ = /[^\d]+$/; + + +/** + * Returns the units used for a CSS length measurement. + * @param {string} value A CSS length quantity. + * @return {?string} The units of measurement. + */ +goog.style.getLengthUnits = function(value) { + var units = value.match(goog.style.lengthUnitRegex_); + return units && units[0] || null; +}; + + +/** + * Map of absolute CSS length units + * @type {Object} + * @private + */ +goog.style.ABSOLUTE_CSS_LENGTH_UNITS_ = { + 'cm' : 1, + 'in' : 1, + 'mm' : 1, + 'pc' : 1, + 'pt' : 1 +}; + + +/** + * Map of relative CSS length units that can be accurately converted to px + * font-size values using getIePixelValue_. Only units that are defined in + * relation to a font size are convertible (%, small, etc. are not). + * @type {Object} + * @private + */ +goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_ = { + 'em' : 1, + 'ex' : 1 +}; + + +/** + * Returns the font size, in pixels, of text in an element. + * @param {Element} el The element whose font size is returned. + * @return {number} The font size (in pixels). + */ +goog.style.getFontSize = function(el) { + var fontSize = goog.style.getStyle_(el, 'fontSize'); + var sizeUnits = goog.style.getLengthUnits(fontSize); + if (fontSize && 'px' == sizeUnits) { + // NOTE(user): This could be parseFloat instead, but IE doesn't return + // decimal fractions in getStyle_ and Firefox reports the fractions, but + // ignores them when rendering. Interestingly enough, when we force the + // issue and size something to e.g., 50% of 25px, the browsers round in + // opposite directions with Firefox reporting 12px and IE 13px. I punt. + return parseInt(fontSize, 10); + } + + // In IE, we can convert absolute length units to a px value using + // goog.style.getIePixelValue_. Units defined in relation to a font size + // (em, ex) are applied relative to the element's parentNode and can also + // be converted. + if (goog.userAgent.IE) { + if (sizeUnits in goog.style.ABSOLUTE_CSS_LENGTH_UNITS_) { + return goog.style.getIePixelValue_(el, + fontSize, + 'left', + 'pixelLeft'); + } else if (el.parentNode && + el.parentNode.nodeType == goog.dom.NodeType.ELEMENT && + sizeUnits in goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_) { + // Check the parent size - if it is the same it means the relative size + // value is inherited and we therefore don't want to count it twice. If + // it is different, this element either has explicit style or has a CSS + // rule applying to it. + var parentElement = /** @type {Element} */ (el.parentNode); + var parentSize = goog.style.getStyle_(parentElement, 'fontSize'); + return goog.style.getIePixelValue_(parentElement, + fontSize == parentSize ? + '1em' : fontSize, + 'left', + 'pixelLeft'); + } + } + + // Sometimes we can't cleanly find the font size (some units relative to a + // node's parent's font size are difficult: %, smaller et al), so we create + // an invisible, absolutely-positioned span sized to be the height of an 'M' + // rendered in its parent's (i.e., our target element's) font size. This is + // the definition of CSS's font size attribute. + var sizeElement = goog.dom.createDom( + 'span', + {'style': 'visibility:hidden;position:absolute;' + + 'line-height:0;padding:0;margin:0;border:0;height:1em;'}); + goog.dom.appendChild(el, sizeElement); + fontSize = sizeElement.offsetHeight; + goog.dom.removeNode(sizeElement); + + return fontSize; +}; + + +/** + * Parses a style attribute value. Converts CSS property names to camel case. + * @param {string} value The style attribute value. + * @return {!Object} Map of CSS properties to string values. + */ +goog.style.parseStyleAttribute = function(value) { + var result = {}; + goog.array.forEach(value.split(/\s*;\s*/), function(pair) { + var keyValue = pair.split(/\s*:\s*/); + if (keyValue.length == 2) { + result[goog.string.toCamelCase(keyValue[0].toLowerCase())] = keyValue[1]; + } + }); + return result; +}; + + +/** + * Reverse of parseStyleAttribute; that is, takes a style object and returns the + * corresponding attribute value. Converts camel case property names to proper + * CSS selector names. + * @param {Object} obj Map of CSS properties to values. + * @return {string} The style attribute value. + */ +goog.style.toStyleAttribute = function(obj) { + var buffer = []; + goog.object.forEach(obj, function(value, key) { + buffer.push(goog.string.toSelectorCase(key), ':', value, ';'); + }); + return buffer.join(''); +}; + + +/** + * Sets CSS float property on an element. + * @param {Element} el The element to set float property on. + * @param {string} value The value of float CSS property to set on this element. + */ +goog.style.setFloat = function(el, value) { + el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] = value; +}; + + +/** + * Gets value of explicitly-set float CSS property on an element. + * @param {Element} el The element to get float property of. + * @return {string} The value of explicitly-set float CSS property on this + * element. + */ +goog.style.getFloat = function(el) { + return el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] || ''; +}; + + +/** + * Returns the scroll bar width (represents the width of both horizontal + * and vertical scroll). + * + * @param {string=} opt_className An optional class name (or names) to apply + * to the invisible div created to measure the scrollbar. This is necessary + * if some scrollbars are styled differently than others. + * @return {number} The scroll bar width in px. + */ +goog.style.getScrollbarWidth = function(opt_className) { + // Add two hidden divs. The child div is larger than the parent and + // forces scrollbars to appear on it. + // Using overflow:scroll does not work consistently with scrollbars that + // are styled with ::-webkit-scrollbar. + var outerDiv = goog.dom.createElement('div'); + if (opt_className) { + outerDiv.className = opt_className; + } + outerDiv.style.cssText = 'overflow:auto;' + + 'position:absolute;top:0;width:100px;height:100px'; + var innerDiv = goog.dom.createElement('div'); + goog.style.setSize(innerDiv, '200px', '200px'); + outerDiv.appendChild(innerDiv); + goog.dom.appendChild(goog.dom.getDocument().body, outerDiv); + var width = outerDiv.offsetWidth - outerDiv.clientWidth; + goog.dom.removeNode(outerDiv); + return width; +}; + + +/** + * Regular expression to extract x and y translation components from a CSS + * transform Matrix representation. + * + * @type {!RegExp} + * @const + * @private + */ +goog.style.MATRIX_TRANSLATION_REGEX_ = + new RegExp('matrix\\([0-9\\.\\-]+, [0-9\\.\\-]+, ' + + '[0-9\\.\\-]+, [0-9\\.\\-]+, ' + + '([0-9\\.\\-]+)p?x?, ([0-9\\.\\-]+)p?x?\\)'); + + +/** + * Returns the x,y translation component of any CSS transforms applied to the + * element, in pixels. + * + * @param {!Element} element The element to get the translation of. + * @return {!goog.math.Coordinate} The CSS translation of the element in px. + */ +goog.style.getCssTranslation = function(element) { + var transform = goog.style.getComputedTransform(element); + if (!transform) { + return new goog.math.Coordinate(0, 0); + } + var matches = transform.match(goog.style.MATRIX_TRANSLATION_REGEX_); + if (!matches) { + return new goog.math.Coordinate(0, 0); + } + return new goog.math.Coordinate(parseFloat(matches[1]), + parseFloat(matches[2])); +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/testing/watchers.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/testing/watchers.js new file mode 100644 index 0000000..a69dc3e --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/testing/watchers.js @@ -0,0 +1,46 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Simple notifiers for the Closure testing framework. + * + * @author johnlenz@google.com (John Lenz) + */ + +goog.provide('goog.testing.watchers'); + + +/** @private {!Array.<function()>} */ +goog.testing.watchers.resetWatchers_ = []; + + +/** + * Fires clock reset watching functions. + */ +goog.testing.watchers.signalClockReset = function() { + var watchers = goog.testing.watchers.resetWatchers_; + for (var i = 0; i < watchers.length; i++) { + goog.testing.watchers.resetWatchers_[i](); + } +}; + + +/** + * Enqueues a function to be called when the clock used for setTimeout is reset. + * @param {function()} fn + */ +goog.testing.watchers.watchClockReset = function(fn) { + goog.testing.watchers.resetWatchers_.push(fn); +}; + diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/timer/timer.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/timer/timer.js new file mode 100644 index 0000000..ccee170 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/timer/timer.js @@ -0,0 +1,292 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A timer class to which other classes and objects can + * listen on. This is only an abstraction above setInterval. + * + * @see ../demos/timers.html + */ + +goog.provide('goog.Timer'); + +goog.require('goog.events.EventTarget'); + + + +/** + * Class for handling timing events. + * + * @param {number=} opt_interval Number of ms between ticks (Default: 1ms). + * @param {Object=} opt_timerObject An object that has setTimeout, setInterval, + * clearTimeout and clearInterval (eg Window). + * @constructor + * @extends {goog.events.EventTarget} + */ +goog.Timer = function(opt_interval, opt_timerObject) { + goog.events.EventTarget.call(this); + + /** + * Number of ms between ticks + * @type {number} + * @private + */ + this.interval_ = opt_interval || 1; + + /** + * An object that implements setTimeout, setInterval, clearTimeout and + * clearInterval. We default to the window object. Changing this on + * goog.Timer.prototype changes the object for all timer instances which can + * be useful if your environment has some other implementation of timers than + * the window object. + * @type {Object} + * @private + */ + this.timerObject_ = opt_timerObject || goog.Timer.defaultTimerObject; + + /** + * Cached tick_ bound to the object for later use in the timer. + * @type {Function} + * @private + */ + this.boundTick_ = goog.bind(this.tick_, this); + + /** + * Firefox browser often fires the timer event sooner + * (sometimes MUCH sooner) than the requested timeout. So we + * compare the time to when the event was last fired, and + * reschedule if appropriate. See also goog.Timer.intervalScale + * @type {number} + * @private + */ + this.last_ = goog.now(); +}; +goog.inherits(goog.Timer, goog.events.EventTarget); + + +/** + * Maximum timeout value. + * + * Timeout values too big to fit into a signed 32-bit integer may cause + * overflow in FF, Safari, and Chrome, resulting in the timeout being + * scheduled immediately. It makes more sense simply not to schedule these + * timeouts, since 24.8 days is beyond a reasonable expectation for the + * browser to stay open. + * + * @type {number} + * @private + */ +goog.Timer.MAX_TIMEOUT_ = 2147483647; + + +/** + * Whether this timer is enabled + * @type {boolean} + */ +goog.Timer.prototype.enabled = false; + + +/** + * An object that implements setTimout, setInterval, clearTimeout and + * clearInterval. We default to the global object. Changing + * goog.Timer.defaultTimerObject changes the object for all timer instances + * which can be useful if your environment has some other implementation of + * timers you'd like to use. + * @type {Object} + */ +goog.Timer.defaultTimerObject = goog.global; + + +/** + * A variable that controls the timer error correction. If the + * timer is called before the requested interval times + * intervalScale, which often happens on mozilla, the timer is + * rescheduled. See also this.last_ + * @type {number} + */ +goog.Timer.intervalScale = 0.8; + + +/** + * Variable for storing the result of setInterval + * @type {?number} + * @private + */ +goog.Timer.prototype.timer_ = null; + + +/** + * Gets the interval of the timer. + * @return {number} interval Number of ms between ticks. + */ +goog.Timer.prototype.getInterval = function() { + return this.interval_; +}; + + +/** + * Sets the interval of the timer. + * @param {number} interval Number of ms between ticks. + */ +goog.Timer.prototype.setInterval = function(interval) { + this.interval_ = interval; + if (this.timer_ && this.enabled) { + // Stop and then start the timer to reset the interval. + this.stop(); + this.start(); + } else if (this.timer_) { + this.stop(); + } +}; + + +/** + * Callback for the setTimeout used by the timer + * @private + */ +goog.Timer.prototype.tick_ = function() { + if (this.enabled) { + var elapsed = goog.now() - this.last_; + if (elapsed > 0 && + elapsed < this.interval_ * goog.Timer.intervalScale) { + this.timer_ = this.timerObject_.setTimeout(this.boundTick_, + this.interval_ - elapsed); + return; + } + + // Prevents setInterval from registering a duplicate timeout when called + // in the timer event handler. + if (this.timer_) { + this.timerObject_.clearTimeout(this.timer_); + this.timer_ = null; + } + + this.dispatchTick(); + // The timer could be stopped in the timer event handler. + if (this.enabled) { + this.timer_ = this.timerObject_.setTimeout(this.boundTick_, + this.interval_); + this.last_ = goog.now(); + } + } +}; + + +/** + * Dispatches the TICK event. This is its own method so subclasses can override. + */ +goog.Timer.prototype.dispatchTick = function() { + this.dispatchEvent(goog.Timer.TICK); +}; + + +/** + * Starts the timer. + */ +goog.Timer.prototype.start = function() { + this.enabled = true; + + // If there is no interval already registered, start it now + if (!this.timer_) { + // IMPORTANT! + // window.setInterval in FireFox has a bug - it fires based on + // absolute time, rather than on relative time. What this means + // is that if a computer is sleeping/hibernating for 24 hours + // and the timer interval was configured to fire every 1000ms, + // then after the PC wakes up the timer will fire, in rapid + // succession, 3600*24 times. + // This bug is described here and is already fixed, but it will + // take time to propagate, so for now I am switching this over + // to setTimeout logic. + // https://bugzilla.mozilla.org/show_bug.cgi?id=376643 + // + this.timer_ = this.timerObject_.setTimeout(this.boundTick_, + this.interval_); + this.last_ = goog.now(); + } +}; + + +/** + * Stops the timer. + */ +goog.Timer.prototype.stop = function() { + this.enabled = false; + if (this.timer_) { + this.timerObject_.clearTimeout(this.timer_); + this.timer_ = null; + } +}; + + +/** @override */ +goog.Timer.prototype.disposeInternal = function() { + goog.Timer.superClass_.disposeInternal.call(this); + this.stop(); + delete this.timerObject_; +}; + + +/** + * Constant for the timer's event type + * @type {string} + */ +goog.Timer.TICK = 'tick'; + + +/** + * Calls the given function once, after the optional pause. + * + * The function is always called asynchronously, even if the delay is 0. This + * is a common trick to schedule a function to run after a batch of browser + * event processing. + * + * @param {function(this:SCOPE)|{handleEvent:function()}|null} listener Function + * or object that has a handleEvent method. + * @param {number=} opt_delay Milliseconds to wait; default is 0. + * @param {SCOPE=} opt_handler Object in whose scope to call the listener. + * @return {number} A handle to the timer ID. + * @template SCOPE + */ +goog.Timer.callOnce = function(listener, opt_delay, opt_handler) { + if (goog.isFunction(listener)) { + if (opt_handler) { + listener = goog.bind(listener, opt_handler); + } + } else if (listener && typeof listener.handleEvent == 'function') { + // using typeof to prevent strict js warning + listener = goog.bind(listener.handleEvent, listener); + } else { + throw Error('Invalid listener argument'); + } + + if (opt_delay > goog.Timer.MAX_TIMEOUT_) { + // Timeouts greater than MAX_INT return immediately due to integer + // overflow in many browsers. Since MAX_INT is 24.8 days, just don't + // schedule anything at all. + return -1; + } else { + return goog.Timer.defaultTimerObject.setTimeout( + listener, opt_delay || 0); + } +}; + + +/** + * Clears a timeout initiated by callOnce + * @param {?number} timerId a timer ID. + */ +goog.Timer.clear = function(timerId) { + goog.Timer.defaultTimerObject.clearTimeout(timerId); +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/component.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/component.js new file mode 100644 index 0000000..fd8656b --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/component.js @@ -0,0 +1,1291 @@ +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Abstract class for all UI components. This defines the standard + * design pattern that all UI components should follow. + * + * @see ../demos/samplecomponent.html + * @see http://code.google.com/p/closure-library/wiki/IntroToComponents + */ + +goog.provide('goog.ui.Component'); +goog.provide('goog.ui.Component.Error'); +goog.provide('goog.ui.Component.EventType'); +goog.provide('goog.ui.Component.State'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.NodeType'); +goog.require('goog.events.EventHandler'); +goog.require('goog.events.EventTarget'); +goog.require('goog.object'); +goog.require('goog.style'); +goog.require('goog.ui.IdGenerator'); + + + +/** + * Default implementation of UI component. + * + * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + * @constructor + * @extends {goog.events.EventTarget} + */ +goog.ui.Component = function(opt_domHelper) { + goog.events.EventTarget.call(this); + /** + * DomHelper used to interact with the document, allowing components to be + * created in a different window. + * @protected {!goog.dom.DomHelper} + * @suppress {underscore|visibility} + */ + this.dom_ = opt_domHelper || goog.dom.getDomHelper(); + + /** + * Whether the component is rendered right-to-left. Right-to-left is set + * lazily when {@link #isRightToLeft} is called the first time, unless it has + * been set by calling {@link #setRightToLeft} explicitly. + * @private {?boolean} + */ + this.rightToLeft_ = goog.ui.Component.defaultRightToLeft_; + + /** + * Unique ID of the component, lazily initialized in {@link + * goog.ui.Component#getId} if needed. This property is strictly private and + * must not be accessed directly outside of this class! + * @private {?string} + */ + this.id_ = null; + + /** + * Whether the component is in the document. + * @private {boolean} + */ + this.inDocument_ = false; + + // TODO(attila): Stop referring to this private field in subclasses. + /** + * The DOM element for the component. + * @private {Element} + */ + this.element_ = null; + + /** + * Event handler. + * TODO(user): rename it to handler_ after all component subclasses in + * inside Google have been cleaned up. + * Code search: http://go/component_code_search + * @private {goog.events.EventHandler|undefined} + */ + this.googUiComponentHandler_ = void 0; + + /** + * Arbitrary data object associated with the component. Such as meta-data. + * @private {*} + */ + this.model_ = null; + + /** + * Parent component to which events will be propagated. This property is + * strictly private and must not be accessed directly outside of this class! + * @private {goog.ui.Component?} + */ + this.parent_ = null; + + /** + * Array of child components. Lazily initialized on first use. Must be kept + * in sync with {@code childIndex_}. This property is strictly private and + * must not be accessed directly outside of this class! + * @private {Array.<goog.ui.Component>?} + */ + this.children_ = null; + + /** + * Map of child component IDs to child components. Used for constant-time + * random access to child components by ID. Lazily initialized on first use. + * Must be kept in sync with {@code children_}. This property is strictly + * private and must not be accessed directly outside of this class! + * + * We use a plain Object, not a {@link goog.structs.Map}, for simplicity. + * This means components can't have children with IDs such as 'constructor' or + * 'valueOf', but this shouldn't really be an issue in practice, and if it is, + * we can always fix it later without changing the API. + * + * @private {Object} + */ + this.childIndex_ = null; + + /** + * Flag used to keep track of whether a component decorated an already + * existing element or whether it created the DOM itself. + * + * If an element is decorated, dispose will leave the node in the document. + * It is up to the app to remove the node. + * + * If an element was rendered, dispose will remove the node automatically. + * + * @private {boolean} + */ + this.wasDecorated_ = false; +}; +goog.inherits(goog.ui.Component, goog.events.EventTarget); + + +/** + * @define {boolean} Whether to support calling decorate with an element that is + * not yet in the document. If true, we check if the element is in the + * document, and avoid calling enterDocument if it isn't. If false, we + * maintain legacy behavior (always call enterDocument from decorate). + */ +goog.define('goog.ui.Component.ALLOW_DETACHED_DECORATION', false); + + +/** + * Generator for unique IDs. + * @type {goog.ui.IdGenerator} + * @private + */ +goog.ui.Component.prototype.idGenerator_ = goog.ui.IdGenerator.getInstance(); + + +// TODO(gboyer): See if we can remove this and just check goog.i18n.bidi.IS_RTL. +/** + * @define {number} Defines the default BIDI directionality. + * 0: Unknown. + * 1: Left-to-right. + * -1: Right-to-left. + */ +goog.define('goog.ui.Component.DEFAULT_BIDI_DIR', 0); + + +/** + * The default right to left value. + * @type {?boolean} + * @private + */ +goog.ui.Component.defaultRightToLeft_ = + (goog.ui.Component.DEFAULT_BIDI_DIR == 1) ? false : + (goog.ui.Component.DEFAULT_BIDI_DIR == -1) ? true : null; + + +/** + * Common events fired by components so that event propagation is useful. Not + * all components are expected to dispatch or listen for all event types. + * Events dispatched before a state transition should be cancelable to prevent + * the corresponding state change. + * @enum {string} + */ +goog.ui.Component.EventType = { + /** Dispatched before the component becomes visible. */ + BEFORE_SHOW: 'beforeshow', + + /** + * Dispatched after the component becomes visible. + * NOTE(user): For goog.ui.Container, this actually fires before containers + * are shown. Use goog.ui.Container.EventType.AFTER_SHOW if you want an event + * that fires after a goog.ui.Container is shown. + */ + SHOW: 'show', + + /** Dispatched before the component becomes hidden. */ + HIDE: 'hide', + + /** Dispatched before the component becomes disabled. */ + DISABLE: 'disable', + + /** Dispatched before the component becomes enabled. */ + ENABLE: 'enable', + + /** Dispatched before the component becomes highlighted. */ + HIGHLIGHT: 'highlight', + + /** Dispatched before the component becomes un-highlighted. */ + UNHIGHLIGHT: 'unhighlight', + + /** Dispatched before the component becomes activated. */ + ACTIVATE: 'activate', + + /** Dispatched before the component becomes deactivated. */ + DEACTIVATE: 'deactivate', + + /** Dispatched before the component becomes selected. */ + SELECT: 'select', + + /** Dispatched before the component becomes un-selected. */ + UNSELECT: 'unselect', + + /** Dispatched before a component becomes checked. */ + CHECK: 'check', + + /** Dispatched before a component becomes un-checked. */ + UNCHECK: 'uncheck', + + /** Dispatched before a component becomes focused. */ + FOCUS: 'focus', + + /** Dispatched before a component becomes blurred. */ + BLUR: 'blur', + + /** Dispatched before a component is opened (expanded). */ + OPEN: 'open', + + /** Dispatched before a component is closed (collapsed). */ + CLOSE: 'close', + + /** Dispatched after a component is moused over. */ + ENTER: 'enter', + + /** Dispatched after a component is moused out of. */ + LEAVE: 'leave', + + /** Dispatched after the user activates the component. */ + ACTION: 'action', + + /** Dispatched after the external-facing state of a component is changed. */ + CHANGE: 'change' +}; + + +/** + * Errors thrown by the component. + * @enum {string} + */ +goog.ui.Component.Error = { + /** + * Error when a method is not supported. + */ + NOT_SUPPORTED: 'Method not supported', + + /** + * Error when the given element can not be decorated. + */ + DECORATE_INVALID: 'Invalid element to decorate', + + /** + * Error when the component is already rendered and another render attempt is + * made. + */ + ALREADY_RENDERED: 'Component already rendered', + + /** + * Error when an attempt is made to set the parent of a component in a way + * that would result in an inconsistent object graph. + */ + PARENT_UNABLE_TO_BE_SET: 'Unable to set parent component', + + /** + * Error when an attempt is made to add a child component at an out-of-bounds + * index. We don't support sparse child arrays. + */ + CHILD_INDEX_OUT_OF_BOUNDS: 'Child component index out of bounds', + + /** + * Error when an attempt is made to remove a child component from a component + * other than its parent. + */ + NOT_OUR_CHILD: 'Child is not in parent component', + + /** + * Error when an operation requiring DOM interaction is made when the + * component is not in the document + */ + NOT_IN_DOCUMENT: 'Operation not supported while component is not in document', + + /** + * Error when an invalid component state is encountered. + */ + STATE_INVALID: 'Invalid component state' +}; + + +/** + * Common component states. Components may have distinct appearance depending + * on what state(s) apply to them. Not all components are expected to support + * all states. + * @enum {number} + */ +goog.ui.Component.State = { + /** + * Union of all supported component states. + */ + ALL: 0xFF, + + /** + * Component is disabled. + * @see goog.ui.Component.EventType.DISABLE + * @see goog.ui.Component.EventType.ENABLE + */ + DISABLED: 0x01, + + /** + * Component is highlighted. + * @see goog.ui.Component.EventType.HIGHLIGHT + * @see goog.ui.Component.EventType.UNHIGHLIGHT + */ + HOVER: 0x02, + + /** + * Component is active (or "pressed"). + * @see goog.ui.Component.EventType.ACTIVATE + * @see goog.ui.Component.EventType.DEACTIVATE + */ + ACTIVE: 0x04, + + /** + * Component is selected. + * @see goog.ui.Component.EventType.SELECT + * @see goog.ui.Component.EventType.UNSELECT + */ + SELECTED: 0x08, + + /** + * Component is checked. + * @see goog.ui.Component.EventType.CHECK + * @see goog.ui.Component.EventType.UNCHECK + */ + CHECKED: 0x10, + + /** + * Component has focus. + * @see goog.ui.Component.EventType.FOCUS + * @see goog.ui.Component.EventType.BLUR + */ + FOCUSED: 0x20, + + /** + * Component is opened (expanded). Applies to tree nodes, menu buttons, + * submenus, zippys (zippies?), etc. + * @see goog.ui.Component.EventType.OPEN + * @see goog.ui.Component.EventType.CLOSE + */ + OPENED: 0x40 +}; + + +/** + * Static helper method; returns the type of event components are expected to + * dispatch when transitioning to or from the given state. + * @param {goog.ui.Component.State} state State to/from which the component + * is transitioning. + * @param {boolean} isEntering Whether the component is entering or leaving the + * state. + * @return {goog.ui.Component.EventType} Event type to dispatch. + */ +goog.ui.Component.getStateTransitionEvent = function(state, isEntering) { + switch (state) { + case goog.ui.Component.State.DISABLED: + return isEntering ? goog.ui.Component.EventType.DISABLE : + goog.ui.Component.EventType.ENABLE; + case goog.ui.Component.State.HOVER: + return isEntering ? goog.ui.Component.EventType.HIGHLIGHT : + goog.ui.Component.EventType.UNHIGHLIGHT; + case goog.ui.Component.State.ACTIVE: + return isEntering ? goog.ui.Component.EventType.ACTIVATE : + goog.ui.Component.EventType.DEACTIVATE; + case goog.ui.Component.State.SELECTED: + return isEntering ? goog.ui.Component.EventType.SELECT : + goog.ui.Component.EventType.UNSELECT; + case goog.ui.Component.State.CHECKED: + return isEntering ? goog.ui.Component.EventType.CHECK : + goog.ui.Component.EventType.UNCHECK; + case goog.ui.Component.State.FOCUSED: + return isEntering ? goog.ui.Component.EventType.FOCUS : + goog.ui.Component.EventType.BLUR; + case goog.ui.Component.State.OPENED: + return isEntering ? goog.ui.Component.EventType.OPEN : + goog.ui.Component.EventType.CLOSE; + default: + // Fall through. + } + + // Invalid state. + throw Error(goog.ui.Component.Error.STATE_INVALID); +}; + + +/** + * Set the default right-to-left value. This causes all component's created from + * this point foward to have the given value. This is useful for cases where + * a given page is always in one directionality, avoiding unnecessary + * right to left determinations. + * @param {?boolean} rightToLeft Whether the components should be rendered + * right-to-left. Null iff components should determine their directionality. + */ +goog.ui.Component.setDefaultRightToLeft = function(rightToLeft) { + goog.ui.Component.defaultRightToLeft_ = rightToLeft; +}; + + +/** + * Gets the unique ID for the instance of this component. If the instance + * doesn't already have an ID, generates one on the fly. + * @return {string} Unique component ID. + */ +goog.ui.Component.prototype.getId = function() { + return this.id_ || (this.id_ = this.idGenerator_.getNextUniqueId()); +}; + + +/** + * Assigns an ID to this component instance. It is the caller's responsibility + * to guarantee that the ID is unique. If the component is a child of a parent + * component, then the parent component's child index is updated to reflect the + * new ID; this may throw an error if the parent already has a child with an ID + * that conflicts with the new ID. + * @param {string} id Unique component ID. + */ +goog.ui.Component.prototype.setId = function(id) { + if (this.parent_ && this.parent_.childIndex_) { + // Update the parent's child index. + goog.object.remove(this.parent_.childIndex_, this.id_); + goog.object.add(this.parent_.childIndex_, id, this); + } + + // Update the component ID. + this.id_ = id; +}; + + +/** + * Gets the component's element. + * @return {Element} The element for the component. + */ +goog.ui.Component.prototype.getElement = function() { + return this.element_; +}; + + +/** + * Gets the component's element. This differs from getElement in that + * it assumes that the element exists (i.e. the component has been + * rendered/decorated) and will cause an assertion error otherwise (if + * assertion is enabled). + * @return {!Element} The element for the component. + */ +goog.ui.Component.prototype.getElementStrict = function() { + var el = this.element_; + goog.asserts.assert( + el, 'Can not call getElementStrict before rendering/decorating.'); + return el; +}; + + +/** + * Sets the component's root element to the given element. Considered + * protected and final. + * + * This should generally only be called during createDom. Setting the element + * does not actually change which element is rendered, only the element that is + * associated with this UI component. + * + * This should only be used by subclasses and its associated renderers. + * + * @param {Element} element Root element for the component. + */ +goog.ui.Component.prototype.setElementInternal = function(element) { + this.element_ = element; +}; + + +/** + * Returns an array of all the elements in this component's DOM with the + * provided className. + * @param {string} className The name of the class to look for. + * @return {!goog.array.ArrayLike} The items found with the class name provided. + */ +goog.ui.Component.prototype.getElementsByClass = function(className) { + return this.element_ ? + this.dom_.getElementsByClass(className, this.element_) : []; +}; + + +/** + * Returns the first element in this component's DOM with the provided + * className. + * @param {string} className The name of the class to look for. + * @return {Element} The first item with the class name provided. + */ +goog.ui.Component.prototype.getElementByClass = function(className) { + return this.element_ ? + this.dom_.getElementByClass(className, this.element_) : null; +}; + + +/** + * Similar to {@code getElementByClass} except that it expects the + * element to be present in the dom thus returning a required value. Otherwise, + * will assert. + * @param {string} className The name of the class to look for. + * @return {!Element} The first item with the class name provided. + */ +goog.ui.Component.prototype.getRequiredElementByClass = function(className) { + var el = this.getElementByClass(className); + goog.asserts.assert(el, 'Expected element in component with class: %s', + className); + return el; +}; + + +/** + * Returns the event handler for this component, lazily created the first time + * this method is called. + * @return {!goog.events.EventHandler.<T>} Event handler for this component. + * @protected + * @this T + * @template T + */ +goog.ui.Component.prototype.getHandler = function() { + if (!this.googUiComponentHandler_) { + this.googUiComponentHandler_ = new goog.events.EventHandler(this); + } + return this.googUiComponentHandler_; +}; + + +/** + * Sets the parent of this component to use for event bubbling. Throws an error + * if the component already has a parent or if an attempt is made to add a + * component to itself as a child. Callers must use {@code removeChild} + * or {@code removeChildAt} to remove components from their containers before + * calling this method. + * @see goog.ui.Component#removeChild + * @see goog.ui.Component#removeChildAt + * @param {goog.ui.Component} parent The parent component. + */ +goog.ui.Component.prototype.setParent = function(parent) { + if (this == parent) { + // Attempting to add a child to itself is an error. + throw Error(goog.ui.Component.Error.PARENT_UNABLE_TO_BE_SET); + } + + if (parent && this.parent_ && this.id_ && this.parent_.getChild(this.id_) && + this.parent_ != parent) { + // This component is already the child of some parent, so it should be + // removed using removeChild/removeChildAt first. + throw Error(goog.ui.Component.Error.PARENT_UNABLE_TO_BE_SET); + } + + this.parent_ = parent; + goog.ui.Component.superClass_.setParentEventTarget.call(this, parent); +}; + + +/** + * Returns the component's parent, if any. + * @return {goog.ui.Component?} The parent component. + */ +goog.ui.Component.prototype.getParent = function() { + return this.parent_; +}; + + +/** + * Overrides {@link goog.events.EventTarget#setParentEventTarget} to throw an + * error if the parent component is set, and the argument is not the parent. + * @override + */ +goog.ui.Component.prototype.setParentEventTarget = function(parent) { + if (this.parent_ && this.parent_ != parent) { + throw Error(goog.ui.Component.Error.NOT_SUPPORTED); + } + goog.ui.Component.superClass_.setParentEventTarget.call(this, parent); +}; + + +/** + * Returns the dom helper that is being used on this component. + * @return {!goog.dom.DomHelper} The dom helper used on this component. + */ +goog.ui.Component.prototype.getDomHelper = function() { + return this.dom_; +}; + + +/** + * Determines whether the component has been added to the document. + * @return {boolean} TRUE if rendered. Otherwise, FALSE. + */ +goog.ui.Component.prototype.isInDocument = function() { + return this.inDocument_; +}; + + +/** + * Creates the initial DOM representation for the component. The default + * implementation is to set this.element_ = div. + */ +goog.ui.Component.prototype.createDom = function() { + this.element_ = this.dom_.createElement('div'); +}; + + +/** + * Renders the component. If a parent element is supplied, the component's + * element will be appended to it. If there is no optional parent element and + * the element doesn't have a parentNode then it will be appended to the + * document body. + * + * If this component has a parent component, and the parent component is + * not in the document already, then this will not call {@code enterDocument} + * on this component. + * + * Throws an Error if the component is already rendered. + * + * @param {Element=} opt_parentElement Optional parent element to render the + * component into. + */ +goog.ui.Component.prototype.render = function(opt_parentElement) { + this.render_(opt_parentElement); +}; + + +/** + * Renders the component before another element. The other element should be in + * the document already. + * + * Throws an Error if the component is already rendered. + * + * @param {Node} sibling Node to render the component before. + */ +goog.ui.Component.prototype.renderBefore = function(sibling) { + this.render_(/** @type {Element} */ (sibling.parentNode), + sibling); +}; + + +/** + * Renders the component. If a parent element is supplied, the component's + * element will be appended to it. If there is no optional parent element and + * the element doesn't have a parentNode then it will be appended to the + * document body. + * + * If this component has a parent component, and the parent component is + * not in the document already, then this will not call {@code enterDocument} + * on this component. + * + * Throws an Error if the component is already rendered. + * + * @param {Element=} opt_parentElement Optional parent element to render the + * component into. + * @param {Node=} opt_beforeNode Node before which the component is to + * be rendered. If left out the node is appended to the parent element. + * @private + */ +goog.ui.Component.prototype.render_ = function(opt_parentElement, + opt_beforeNode) { + if (this.inDocument_) { + throw Error(goog.ui.Component.Error.ALREADY_RENDERED); + } + + if (!this.element_) { + this.createDom(); + } + + if (opt_parentElement) { + opt_parentElement.insertBefore(this.element_, opt_beforeNode || null); + } else { + this.dom_.getDocument().body.appendChild(this.element_); + } + + // If this component has a parent component that isn't in the document yet, + // we don't call enterDocument() here. Instead, when the parent component + // enters the document, the enterDocument() call will propagate to its + // children, including this one. If the component doesn't have a parent + // or if the parent is already in the document, we call enterDocument(). + if (!this.parent_ || this.parent_.isInDocument()) { + this.enterDocument(); + } +}; + + +/** + * Decorates the element for the UI component. If the element is in the + * document, the enterDocument method will be called. + * + * If goog.ui.Component.ALLOW_DETACHED_DECORATION is false, the caller must + * pass an element that is in the document. + * + * @param {Element} element Element to decorate. + */ +goog.ui.Component.prototype.decorate = function(element) { + if (this.inDocument_) { + throw Error(goog.ui.Component.Error.ALREADY_RENDERED); + } else if (element && this.canDecorate(element)) { + this.wasDecorated_ = true; + + // Set the DOM helper of the component to match the decorated element. + var doc = goog.dom.getOwnerDocument(element); + if (!this.dom_ || this.dom_.getDocument() != doc) { + this.dom_ = goog.dom.getDomHelper(element); + } + + // Call specific component decorate logic. + this.decorateInternal(element); + + // If supporting detached decoration, check that element is in doc. + if (!goog.ui.Component.ALLOW_DETACHED_DECORATION || + goog.dom.contains(doc, element)) { + this.enterDocument(); + } + } else { + throw Error(goog.ui.Component.Error.DECORATE_INVALID); + } +}; + + +/** + * Determines if a given element can be decorated by this type of component. + * This method should be overridden by inheriting objects. + * @param {Element} element Element to decorate. + * @return {boolean} True if the element can be decorated, false otherwise. + */ +goog.ui.Component.prototype.canDecorate = function(element) { + return true; +}; + + +/** + * @return {boolean} Whether the component was decorated. + */ +goog.ui.Component.prototype.wasDecorated = function() { + return this.wasDecorated_; +}; + + +/** + * Actually decorates the element. Should be overridden by inheriting objects. + * This method can assume there are checks to ensure the component has not + * already been rendered have occurred and that enter document will be called + * afterwards. This method is considered protected. + * @param {Element} element Element to decorate. + * @protected + */ +goog.ui.Component.prototype.decorateInternal = function(element) { + this.element_ = element; +}; + + +/** + * Called when the component's element is known to be in the document. Anything + * using document.getElementById etc. should be done at this stage. + * + * If the component contains child components, this call is propagated to its + * children. + */ +goog.ui.Component.prototype.enterDocument = function() { + this.inDocument_ = true; + + // Propagate enterDocument to child components that have a DOM, if any. + // If a child was decorated before entering the document (permitted when + // goog.ui.Component.ALLOW_DETACHED_DECORATION is true), its enterDocument + // will be called here. + this.forEachChild(function(child) { + if (!child.isInDocument() && child.getElement()) { + child.enterDocument(); + } + }); +}; + + +/** + * Called by dispose to clean up the elements and listeners created by a + * component, or by a parent component/application who has removed the + * component from the document but wants to reuse it later. + * + * If the component contains child components, this call is propagated to its + * children. + * + * It should be possible for the component to be rendered again once this method + * has been called. + */ +goog.ui.Component.prototype.exitDocument = function() { + // Propagate exitDocument to child components that have been rendered, if any. + this.forEachChild(function(child) { + if (child.isInDocument()) { + child.exitDocument(); + } + }); + + if (this.googUiComponentHandler_) { + this.googUiComponentHandler_.removeAll(); + } + + this.inDocument_ = false; +}; + + +/** + * Disposes of the component. Calls {@code exitDocument}, which is expected to + * remove event handlers and clean up the component. Propagates the call to + * the component's children, if any. Removes the component's DOM from the + * document unless it was decorated. + * @override + * @protected + */ +goog.ui.Component.prototype.disposeInternal = function() { + if (this.inDocument_) { + this.exitDocument(); + } + + if (this.googUiComponentHandler_) { + this.googUiComponentHandler_.dispose(); + delete this.googUiComponentHandler_; + } + + // Disposes of the component's children, if any. + this.forEachChild(function(child) { + child.dispose(); + }); + + // Detach the component's element from the DOM, unless it was decorated. + if (!this.wasDecorated_ && this.element_) { + goog.dom.removeNode(this.element_); + } + + this.children_ = null; + this.childIndex_ = null; + this.element_ = null; + this.model_ = null; + this.parent_ = null; + + goog.ui.Component.superClass_.disposeInternal.call(this); +}; + + +/** + * Helper function for subclasses that gets a unique id for a given fragment, + * this can be used by components to generate unique string ids for DOM + * elements. + * @param {string} idFragment A partial id. + * @return {string} Unique element id. + */ +goog.ui.Component.prototype.makeId = function(idFragment) { + return this.getId() + '.' + idFragment; +}; + + +/** + * Makes a collection of ids. This is a convenience method for makeId. The + * object's values are the id fragments and the new values are the generated + * ids. The key will remain the same. + * @param {Object} object The object that will be used to create the ids. + * @return {!Object} An object of id keys to generated ids. + */ +goog.ui.Component.prototype.makeIds = function(object) { + var ids = {}; + for (var key in object) { + ids[key] = this.makeId(object[key]); + } + return ids; +}; + + +/** + * Returns the model associated with the UI component. + * @return {*} The model. + */ +goog.ui.Component.prototype.getModel = function() { + return this.model_; +}; + + +/** + * Sets the model associated with the UI component. + * @param {*} obj The model. + */ +goog.ui.Component.prototype.setModel = function(obj) { + this.model_ = obj; +}; + + +/** + * Helper function for returning the fragment portion of an id generated using + * makeId(). + * @param {string} id Id generated with makeId(). + * @return {string} Fragment. + */ +goog.ui.Component.prototype.getFragmentFromId = function(id) { + return id.substring(this.getId().length + 1); +}; + + +/** + * Helper function for returning an element in the document with a unique id + * generated using makeId(). + * @param {string} idFragment The partial id. + * @return {Element} The element with the unique id, or null if it cannot be + * found. + */ +goog.ui.Component.prototype.getElementByFragment = function(idFragment) { + if (!this.inDocument_) { + throw Error(goog.ui.Component.Error.NOT_IN_DOCUMENT); + } + return this.dom_.getElement(this.makeId(idFragment)); +}; + + +/** + * Adds the specified component as the last child of this component. See + * {@link goog.ui.Component#addChildAt} for detailed semantics. + * + * @see goog.ui.Component#addChildAt + * @param {goog.ui.Component} child The new child component. + * @param {boolean=} opt_render If true, the child component will be rendered + * into the parent. + */ +goog.ui.Component.prototype.addChild = function(child, opt_render) { + // TODO(gboyer): addChildAt(child, this.getChildCount(), false) will + // reposition any already-rendered child to the end. Instead, perhaps + // addChild(child, false) should never reposition the child; instead, clients + // that need the repositioning will use addChildAt explicitly. Right now, + // clients can get around this by calling addChild before calling decorate. + this.addChildAt(child, this.getChildCount(), opt_render); +}; + + +/** + * Adds the specified component as a child of this component at the given + * 0-based index. + * + * Both {@code addChild} and {@code addChildAt} assume the following contract + * between parent and child components: + * <ul> + * <li>the child component's element must be a descendant of the parent + * component's element, and + * <li>the DOM state of the child component must be consistent with the DOM + * state of the parent component (see {@code isInDocument}) in the + * steady state -- the exception is to addChildAt(child, i, false) and + * then immediately decorate/render the child. + * </ul> + * + * In particular, {@code parent.addChild(child)} will throw an error if the + * child component is already in the document, but the parent isn't. + * + * Clients of this API may call {@code addChild} and {@code addChildAt} with + * {@code opt_render} set to true. If {@code opt_render} is true, calling these + * methods will automatically render the child component's element into the + * parent component's element. If the parent does not yet have an element, then + * {@code createDom} will automatically be invoked on the parent before + * rendering the child. + * + * Invoking {@code parent.addChild(child, true)} will throw an error if the + * child component is already in the document, regardless of the parent's DOM + * state. + * + * If {@code opt_render} is true and the parent component is not already + * in the document, {@code enterDocument} will not be called on this component + * at this point. + * + * Finally, this method also throws an error if the new child already has a + * different parent, or the given index is out of bounds. + * + * @see goog.ui.Component#addChild + * @param {goog.ui.Component} child The new child component. + * @param {number} index 0-based index at which the new child component is to be + * added; must be between 0 and the current child count (inclusive). + * @param {boolean=} opt_render If true, the child component will be rendered + * into the parent. + * @return {void} Nada. + */ +goog.ui.Component.prototype.addChildAt = function(child, index, opt_render) { + goog.asserts.assert(!!child, 'Provided element must not be null.'); + + if (child.inDocument_ && (opt_render || !this.inDocument_)) { + // Adding a child that's already in the document is an error, except if the + // parent is also in the document and opt_render is false (e.g. decorate()). + throw Error(goog.ui.Component.Error.ALREADY_RENDERED); + } + + if (index < 0 || index > this.getChildCount()) { + // Allowing sparse child arrays would lead to strange behavior, so we don't. + throw Error(goog.ui.Component.Error.CHILD_INDEX_OUT_OF_BOUNDS); + } + + // Create the index and the child array on first use. + if (!this.childIndex_ || !this.children_) { + this.childIndex_ = {}; + this.children_ = []; + } + + // Moving child within component, remove old reference. + if (child.getParent() == this) { + goog.object.set(this.childIndex_, child.getId(), child); + goog.array.remove(this.children_, child); + + // Add the child to this component. goog.object.add() throws an error if + // a child with the same ID already exists. + } else { + goog.object.add(this.childIndex_, child.getId(), child); + } + + // Set the parent of the child to this component. This throws an error if + // the child is already contained by another component. + child.setParent(this); + goog.array.insertAt(this.children_, child, index); + + if (child.inDocument_ && this.inDocument_ && child.getParent() == this) { + // Changing the position of an existing child, move the DOM node. + var contentElement = this.getContentElement(); + contentElement.insertBefore(child.getElement(), + (contentElement.childNodes[index] || null)); + + } else if (opt_render) { + // If this (parent) component doesn't have a DOM yet, call createDom now + // to make sure we render the child component's element into the correct + // parent element (otherwise render_ with a null first argument would + // render the child into the document body, which is almost certainly not + // what we want). + if (!this.element_) { + this.createDom(); + } + // Render the child into the parent at the appropriate location. Note that + // getChildAt(index + 1) returns undefined if inserting at the end. + // TODO(attila): We should have a renderer with a renderChildAt API. + var sibling = this.getChildAt(index + 1); + // render_() calls enterDocument() if the parent is already in the document. + child.render_(this.getContentElement(), sibling ? sibling.element_ : null); + } else if (this.inDocument_ && !child.inDocument_ && child.element_ && + child.element_.parentNode && + // Under some circumstances, IE8 implicitly creates a Document Fragment + // for detached nodes, so ensure the parent is an Element as it should be. + child.element_.parentNode.nodeType == goog.dom.NodeType.ELEMENT) { + // We don't touch the DOM, but if the parent is in the document, and the + // child element is in the document but not marked as such, then we call + // enterDocument on the child. + // TODO(gboyer): It would be nice to move this condition entirely, but + // there's a large risk of breaking existing applications that manually + // append the child to the DOM and then call addChild. + child.enterDocument(); + } +}; + + +/** + * Returns the DOM element into which child components are to be rendered, + * or null if the component itself hasn't been rendered yet. This default + * implementation returns the component's root element. Subclasses with + * complex DOM structures must override this method. + * @return {Element} Element to contain child elements (null if none). + */ +goog.ui.Component.prototype.getContentElement = function() { + return this.element_; +}; + + +/** + * Returns true if the component is rendered right-to-left, false otherwise. + * The first time this function is invoked, the right-to-left rendering property + * is set if it has not been already. + * @return {boolean} Whether the control is rendered right-to-left. + */ +goog.ui.Component.prototype.isRightToLeft = function() { + if (this.rightToLeft_ == null) { + this.rightToLeft_ = goog.style.isRightToLeft(this.inDocument_ ? + this.element_ : this.dom_.getDocument().body); + } + return /** @type {boolean} */(this.rightToLeft_); +}; + + +/** + * Set is right-to-left. This function should be used if the component needs + * to know the rendering direction during dom creation (i.e. before + * {@link #enterDocument} is called and is right-to-left is set). + * @param {boolean} rightToLeft Whether the component is rendered + * right-to-left. + */ +goog.ui.Component.prototype.setRightToLeft = function(rightToLeft) { + if (this.inDocument_) { + throw Error(goog.ui.Component.Error.ALREADY_RENDERED); + } + this.rightToLeft_ = rightToLeft; +}; + + +/** + * Returns true if the component has children. + * @return {boolean} True if the component has children. + */ +goog.ui.Component.prototype.hasChildren = function() { + return !!this.children_ && this.children_.length != 0; +}; + + +/** + * Returns the number of children of this component. + * @return {number} The number of children. + */ +goog.ui.Component.prototype.getChildCount = function() { + return this.children_ ? this.children_.length : 0; +}; + + +/** + * Returns an array containing the IDs of the children of this component, or an + * empty array if the component has no children. + * @return {!Array.<string>} Child component IDs. + */ +goog.ui.Component.prototype.getChildIds = function() { + var ids = []; + + // We don't use goog.object.getKeys(this.childIndex_) because we want to + // return the IDs in the correct order as determined by this.children_. + this.forEachChild(function(child) { + // addChild()/addChildAt() guarantee that the child array isn't sparse. + ids.push(child.getId()); + }); + + return ids; +}; + + +/** + * Returns the child with the given ID, or null if no such child exists. + * @param {string} id Child component ID. + * @return {goog.ui.Component?} The child with the given ID; null if none. + */ +goog.ui.Component.prototype.getChild = function(id) { + // Use childIndex_ for O(1) access by ID. + return (this.childIndex_ && id) ? /** @type {goog.ui.Component} */ ( + goog.object.get(this.childIndex_, id)) || null : null; +}; + + +/** + * Returns the child at the given index, or null if the index is out of bounds. + * @param {number} index 0-based index. + * @return {goog.ui.Component?} The child at the given index; null if none. + */ +goog.ui.Component.prototype.getChildAt = function(index) { + // Use children_ for access by index. + return this.children_ ? this.children_[index] || null : null; +}; + + +/** + * Calls the given function on each of this component's children in order. If + * {@code opt_obj} is provided, it will be used as the 'this' object in the + * function when called. The function should take two arguments: the child + * component and its 0-based index. The return value is ignored. + * @param {function(this:T,?,number):?} f The function to call for every + * child component; should take 2 arguments (the child and its index). + * @param {T=} opt_obj Used as the 'this' object in f when called. + * @template T + */ +goog.ui.Component.prototype.forEachChild = function(f, opt_obj) { + if (this.children_) { + goog.array.forEach(this.children_, f, opt_obj); + } +}; + + +/** + * Returns the 0-based index of the given child component, or -1 if no such + * child is found. + * @param {goog.ui.Component?} child The child component. + * @return {number} 0-based index of the child component; -1 if not found. + */ +goog.ui.Component.prototype.indexOfChild = function(child) { + return (this.children_ && child) ? goog.array.indexOf(this.children_, child) : + -1; +}; + + +/** + * Removes the given child from this component, and returns it. Throws an error + * if the argument is invalid or if the specified child isn't found in the + * parent component. The argument can either be a string (interpreted as the + * ID of the child component to remove) or the child component itself. + * + * If {@code opt_unrender} is true, calls {@link goog.ui.component#exitDocument} + * on the removed child, and subsequently detaches the child's DOM from the + * document. Otherwise it is the caller's responsibility to clean up the child + * component's DOM. + * + * @see goog.ui.Component#removeChildAt + * @param {string|goog.ui.Component|null} child The ID of the child to remove, + * or the child component itself. + * @param {boolean=} opt_unrender If true, calls {@code exitDocument} on the + * removed child component, and detaches its DOM from the document. + * @return {goog.ui.Component} The removed component, if any. + */ +goog.ui.Component.prototype.removeChild = function(child, opt_unrender) { + if (child) { + // Normalize child to be the object and id to be the ID string. This also + // ensures that the child is really ours. + var id = goog.isString(child) ? child : child.getId(); + child = this.getChild(id); + + if (id && child) { + goog.object.remove(this.childIndex_, id); + goog.array.remove(this.children_, child); + + if (opt_unrender) { + // Remove the child component's DOM from the document. We have to call + // exitDocument first (see documentation). + child.exitDocument(); + if (child.element_) { + goog.dom.removeNode(child.element_); + } + } + + // Child's parent must be set to null after exitDocument is called + // so that the child can unlisten to its parent if required. + child.setParent(null); + } + } + + if (!child) { + throw Error(goog.ui.Component.Error.NOT_OUR_CHILD); + } + + return /** @type {goog.ui.Component} */(child); +}; + + +/** + * Removes the child at the given index from this component, and returns it. + * Throws an error if the argument is out of bounds, or if the specified child + * isn't found in the parent. See {@link goog.ui.Component#removeChild} for + * detailed semantics. + * + * @see goog.ui.Component#removeChild + * @param {number} index 0-based index of the child to remove. + * @param {boolean=} opt_unrender If true, calls {@code exitDocument} on the + * removed child component, and detaches its DOM from the document. + * @return {goog.ui.Component} The removed component, if any. + */ +goog.ui.Component.prototype.removeChildAt = function(index, opt_unrender) { + // removeChild(null) will throw error. + return this.removeChild(this.getChildAt(index), opt_unrender); +}; + + +/** + * Removes every child component attached to this one and returns them. + * + * @see goog.ui.Component#removeChild + * @param {boolean=} opt_unrender If true, calls {@link #exitDocument} on the + * removed child components, and detaches their DOM from the document. + * @return {!Array.<goog.ui.Component>} The removed components if any. + */ +goog.ui.Component.prototype.removeChildren = function(opt_unrender) { + var removedChildren = []; + while (this.hasChildren()) { + removedChildren.push(this.removeChildAt(0, opt_unrender)); + } + return removedChildren; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/container.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/container.js new file mode 100644 index 0000000..b33947b --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/container.js @@ -0,0 +1,1324 @@ +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Base class for containers that host {@link goog.ui.Control}s, + * such as menus and toolbars. Provides default keyboard and mouse event + * handling and child management, based on a generalized version of + * {@link goog.ui.Menu}. + * + * @author attila@google.com (Attila Bodis) + * @see ../demos/container.html + */ +// TODO(attila): Fix code/logic duplication between this and goog.ui.Control. +// TODO(attila): Maybe pull common stuff all the way up into Component...? + +goog.provide('goog.ui.Container'); +goog.provide('goog.ui.Container.EventType'); +goog.provide('goog.ui.Container.Orientation'); + +goog.require('goog.a11y.aria'); +goog.require('goog.a11y.aria.State'); +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.events.EventType'); +goog.require('goog.events.KeyCodes'); +goog.require('goog.events.KeyHandler'); +goog.require('goog.object'); +goog.require('goog.style'); +goog.require('goog.ui.Component'); +goog.require('goog.ui.ContainerRenderer'); +goog.require('goog.ui.Control'); + + + +/** + * Base class for containers. Extends {@link goog.ui.Component} by adding + * the following: + * <ul> + * <li>a {@link goog.events.KeyHandler}, to simplify keyboard handling, + * <li>a pluggable <em>renderer</em> framework, to simplify the creation of + * containers without the need to subclass this class, + * <li>methods to manage child controls hosted in the container, + * <li>default mouse and keyboard event handling methods. + * </ul> + * @param {?goog.ui.Container.Orientation=} opt_orientation Container + * orientation; defaults to {@code VERTICAL}. + * @param {goog.ui.ContainerRenderer=} opt_renderer Renderer used to render or + * decorate the container; defaults to {@link goog.ui.ContainerRenderer}. + * @param {goog.dom.DomHelper=} opt_domHelper DOM helper, used for document + * interaction. + * @extends {goog.ui.Component} + * @constructor + */ +goog.ui.Container = function(opt_orientation, opt_renderer, opt_domHelper) { + goog.ui.Component.call(this, opt_domHelper); + this.renderer_ = opt_renderer || goog.ui.ContainerRenderer.getInstance(); + this.orientation_ = opt_orientation || this.renderer_.getDefaultOrientation(); +}; +goog.inherits(goog.ui.Container, goog.ui.Component); +goog.tagUnsealableClass(goog.ui.Container); + + +/** + * Container-specific events. + * @enum {string} + */ +goog.ui.Container.EventType = { + /** + * Dispatched after a goog.ui.Container becomes visible. Non-cancellable. + * NOTE(user): This event really shouldn't exist, because the + * goog.ui.Component.EventType.SHOW event should behave like this one. But the + * SHOW event for containers has been behaving as other components' + * BEFORE_SHOW event for a long time, and too much code relies on that old + * behavior to fix it now. + */ + AFTER_SHOW: 'aftershow', + + /** + * Dispatched after a goog.ui.Container becomes invisible. Non-cancellable. + */ + AFTER_HIDE: 'afterhide' +}; + + +/** + * Container orientation constants. + * @enum {string} + */ +goog.ui.Container.Orientation = { + HORIZONTAL: 'horizontal', + VERTICAL: 'vertical' +}; + + +/** + * Allows an alternative element to be set to receive key events, otherwise + * defers to the renderer's element choice. + * @type {Element|undefined} + * @private + */ +goog.ui.Container.prototype.keyEventTarget_ = null; + + +/** + * Keyboard event handler. + * @type {goog.events.KeyHandler?} + * @private + */ +goog.ui.Container.prototype.keyHandler_ = null; + + +/** + * Renderer for the container. Defaults to {@link goog.ui.ContainerRenderer}. + * @type {goog.ui.ContainerRenderer?} + * @private + */ +goog.ui.Container.prototype.renderer_ = null; + + +/** + * Container orientation; determines layout and default keyboard navigation. + * @type {?goog.ui.Container.Orientation} + * @private + */ +goog.ui.Container.prototype.orientation_ = null; + + +/** + * Whether the container is set to be visible. Defaults to true. + * @type {boolean} + * @private + */ +goog.ui.Container.prototype.visible_ = true; + + +/** + * Whether the container is enabled and reacting to keyboard and mouse events. + * Defaults to true. + * @type {boolean} + * @private + */ +goog.ui.Container.prototype.enabled_ = true; + + +/** + * Whether the container supports keyboard focus. Defaults to true. Focusable + * containers have a {@code tabIndex} and can be navigated to via the keyboard. + * @type {boolean} + * @private + */ +goog.ui.Container.prototype.focusable_ = true; + + +/** + * The 0-based index of the currently highlighted control in the container + * (-1 if none). + * @type {number} + * @private + */ +goog.ui.Container.prototype.highlightedIndex_ = -1; + + +/** + * The currently open (expanded) control in the container (null if none). + * @type {goog.ui.Control?} + * @private + */ +goog.ui.Container.prototype.openItem_ = null; + + +/** + * Whether the mouse button is held down. Defaults to false. This flag is set + * when the user mouses down over the container, and remains set until they + * release the mouse button. + * @type {boolean} + * @private + */ +goog.ui.Container.prototype.mouseButtonPressed_ = false; + + +/** + * Whether focus of child components should be allowed. Only effective if + * focusable_ is set to false. + * @type {boolean} + * @private + */ +goog.ui.Container.prototype.allowFocusableChildren_ = false; + + +/** + * Whether highlighting a child component should also open it. + * @type {boolean} + * @private + */ +goog.ui.Container.prototype.openFollowsHighlight_ = true; + + +/** + * Map of DOM IDs to child controls. Each key is the DOM ID of a child + * control's root element; each value is a reference to the child control + * itself. Used for looking up the child control corresponding to a DOM + * node in O(1) time. + * @type {Object} + * @private + */ +goog.ui.Container.prototype.childElementIdMap_ = null; + + +// Event handler and renderer management. + + +/** + * Returns the DOM element on which the container is listening for keyboard + * events (null if none). + * @return {Element} Element on which the container is listening for key + * events. + */ +goog.ui.Container.prototype.getKeyEventTarget = function() { + // Delegate to renderer, unless we've set an explicit target. + return this.keyEventTarget_ || this.renderer_.getKeyEventTarget(this); +}; + + +/** + * Attaches an element on which to listen for key events. + * @param {Element|undefined} element The element to attach, or null/undefined + * to attach to the default element. + */ +goog.ui.Container.prototype.setKeyEventTarget = function(element) { + if (this.focusable_) { + var oldTarget = this.getKeyEventTarget(); + var inDocument = this.isInDocument(); + + this.keyEventTarget_ = element; + var newTarget = this.getKeyEventTarget(); + + if (inDocument) { + // Unlisten for events on the old key target. Requires us to reset + // key target state temporarily. + this.keyEventTarget_ = oldTarget; + this.enableFocusHandling_(false); + this.keyEventTarget_ = element; + + // Listen for events on the new key target. + this.getKeyHandler().attach(newTarget); + this.enableFocusHandling_(true); + } + } else { + throw Error('Can\'t set key event target for container ' + + 'that doesn\'t support keyboard focus!'); + } +}; + + +/** + * Returns the keyboard event handler for this container, lazily created the + * first time this method is called. The keyboard event handler listens for + * keyboard events on the container's key event target, as determined by its + * renderer. + * @return {!goog.events.KeyHandler} Keyboard event handler for this container. + */ +goog.ui.Container.prototype.getKeyHandler = function() { + return this.keyHandler_ || + (this.keyHandler_ = new goog.events.KeyHandler(this.getKeyEventTarget())); +}; + + +/** + * Returns the renderer used by this container to render itself or to decorate + * an existing element. + * @return {goog.ui.ContainerRenderer} Renderer used by the container. + */ +goog.ui.Container.prototype.getRenderer = function() { + return this.renderer_; +}; + + +/** + * Registers the given renderer with the container. Changing renderers after + * the container has already been rendered or decorated is an error. + * @param {goog.ui.ContainerRenderer} renderer Renderer used by the container. + */ +goog.ui.Container.prototype.setRenderer = function(renderer) { + if (this.getElement()) { + // Too late. + throw Error(goog.ui.Component.Error.ALREADY_RENDERED); + } + + this.renderer_ = renderer; +}; + + +// Standard goog.ui.Component implementation. + + +/** + * Creates the container's DOM. + * @override + */ +goog.ui.Container.prototype.createDom = function() { + // Delegate to renderer. + this.setElementInternal(this.renderer_.createDom(this)); +}; + + +/** + * Returns the DOM element into which child components are to be rendered, + * or null if the container itself hasn't been rendered yet. Overrides + * {@link goog.ui.Component#getContentElement} by delegating to the renderer. + * @return {Element} Element to contain child elements (null if none). + * @override + */ +goog.ui.Container.prototype.getContentElement = function() { + // Delegate to renderer. + return this.renderer_.getContentElement(this.getElement()); +}; + + +/** + * Returns true if the given element can be decorated by this container. + * Overrides {@link goog.ui.Component#canDecorate}. + * @param {Element} element Element to decorate. + * @return {boolean} True iff the element can be decorated. + * @override + */ +goog.ui.Container.prototype.canDecorate = function(element) { + // Delegate to renderer. + return this.renderer_.canDecorate(element); +}; + + +/** + * Decorates the given element with this container. Overrides {@link + * goog.ui.Component#decorateInternal}. Considered protected. + * @param {Element} element Element to decorate. + * @override + */ +goog.ui.Container.prototype.decorateInternal = function(element) { + // Delegate to renderer. + this.setElementInternal(this.renderer_.decorate(this, element)); + // Check whether the decorated element is explicitly styled to be invisible. + if (element.style.display == 'none') { + this.visible_ = false; + } +}; + + +/** + * Configures the container after its DOM has been rendered, and sets up event + * handling. Overrides {@link goog.ui.Component#enterDocument}. + * @override + */ +goog.ui.Container.prototype.enterDocument = function() { + goog.ui.Container.superClass_.enterDocument.call(this); + + this.forEachChild(function(child) { + if (child.isInDocument()) { + this.registerChildId_(child); + } + }, this); + + var elem = this.getElement(); + + // Call the renderer's initializeDom method to initialize the container's DOM. + this.renderer_.initializeDom(this); + + // Initialize visibility (opt_force = true, so we don't dispatch events). + this.setVisible(this.visible_, true); + + // Handle events dispatched by child controls. + this.getHandler(). + listen(this, goog.ui.Component.EventType.ENTER, + this.handleEnterItem). + listen(this, goog.ui.Component.EventType.HIGHLIGHT, + this.handleHighlightItem). + listen(this, goog.ui.Component.EventType.UNHIGHLIGHT, + this.handleUnHighlightItem). + listen(this, goog.ui.Component.EventType.OPEN, this.handleOpenItem). + listen(this, goog.ui.Component.EventType.CLOSE, this.handleCloseItem). + + // Handle mouse events. + listen(elem, goog.events.EventType.MOUSEDOWN, this.handleMouseDown). + listen(goog.dom.getOwnerDocument(elem), goog.events.EventType.MOUSEUP, + this.handleDocumentMouseUp). + + // Handle mouse events on behalf of controls in the container. + listen(elem, [ + goog.events.EventType.MOUSEDOWN, + goog.events.EventType.MOUSEUP, + goog.events.EventType.MOUSEOVER, + goog.events.EventType.MOUSEOUT, + goog.events.EventType.CONTEXTMENU + ], this.handleChildMouseEvents); + + // If the container is focusable, set up keyboard event handling. + if (this.isFocusable()) { + this.enableFocusHandling_(true); + } +}; + + +/** + * Sets up listening for events applicable to focusable containers. + * @param {boolean} enable Whether to enable or disable focus handling. + * @private + */ +goog.ui.Container.prototype.enableFocusHandling_ = function(enable) { + var handler = this.getHandler(); + var keyTarget = this.getKeyEventTarget(); + if (enable) { + handler. + listen(keyTarget, goog.events.EventType.FOCUS, this.handleFocus). + listen(keyTarget, goog.events.EventType.BLUR, this.handleBlur). + listen(this.getKeyHandler(), goog.events.KeyHandler.EventType.KEY, + this.handleKeyEvent); + } else { + handler. + unlisten(keyTarget, goog.events.EventType.FOCUS, this.handleFocus). + unlisten(keyTarget, goog.events.EventType.BLUR, this.handleBlur). + unlisten(this.getKeyHandler(), goog.events.KeyHandler.EventType.KEY, + this.handleKeyEvent); + } +}; + + +/** + * Cleans up the container before its DOM is removed from the document, and + * removes event handlers. Overrides {@link goog.ui.Component#exitDocument}. + * @override + */ +goog.ui.Container.prototype.exitDocument = function() { + // {@link #setHighlightedIndex} has to be called before + // {@link goog.ui.Component#exitDocument}, otherwise it has no effect. + this.setHighlightedIndex(-1); + + if (this.openItem_) { + this.openItem_.setOpen(false); + } + + this.mouseButtonPressed_ = false; + + goog.ui.Container.superClass_.exitDocument.call(this); +}; + + +/** @override */ +goog.ui.Container.prototype.disposeInternal = function() { + goog.ui.Container.superClass_.disposeInternal.call(this); + + if (this.keyHandler_) { + this.keyHandler_.dispose(); + this.keyHandler_ = null; + } + + this.keyEventTarget_ = null; + this.childElementIdMap_ = null; + this.openItem_ = null; + this.renderer_ = null; +}; + + +// Default event handlers. + + +/** + * Handles ENTER events raised by child controls when they are navigated to. + * @param {goog.events.Event} e ENTER event to handle. + * @return {boolean} Whether to prevent handleMouseOver from handling + * the event. + */ +goog.ui.Container.prototype.handleEnterItem = function(e) { + // Allow the Control to highlight itself. + return true; +}; + + +/** + * Handles HIGHLIGHT events dispatched by items in the container when + * they are highlighted. + * @param {goog.events.Event} e Highlight event to handle. + */ +goog.ui.Container.prototype.handleHighlightItem = function(e) { + var index = this.indexOfChild(/** @type {goog.ui.Control} */ (e.target)); + if (index > -1 && index != this.highlightedIndex_) { + var item = this.getHighlighted(); + if (item) { + // Un-highlight previously highlighted item. + item.setHighlighted(false); + } + + this.highlightedIndex_ = index; + item = this.getHighlighted(); + + if (this.isMouseButtonPressed()) { + // Activate item when mouse button is pressed, to allow MacOS-style + // dragging to choose menu items. Although this should only truly + // happen if the highlight is due to mouse movements, there is little + // harm in doing it for keyboard or programmatic highlights. + item.setActive(true); + } + + // Update open item if open item needs follow highlight. + if (this.openFollowsHighlight_ && + this.openItem_ && item != this.openItem_) { + if (item.isSupportedState(goog.ui.Component.State.OPENED)) { + item.setOpen(true); + } else { + this.openItem_.setOpen(false); + } + } + } + + var element = this.getElement(); + goog.asserts.assert(element, + 'The DOM element for the container cannot be null.'); + if (e.target.getElement() != null) { + goog.a11y.aria.setState(element, + goog.a11y.aria.State.ACTIVEDESCENDANT, + e.target.getElement().id); + } +}; + + +/** + * Handles UNHIGHLIGHT events dispatched by items in the container when + * they are unhighlighted. + * @param {goog.events.Event} e Unhighlight event to handle. + */ +goog.ui.Container.prototype.handleUnHighlightItem = function(e) { + if (e.target == this.getHighlighted()) { + this.highlightedIndex_ = -1; + } + var element = this.getElement(); + goog.asserts.assert(element, + 'The DOM element for the container cannot be null.'); + // Setting certain ARIA attributes to empty strings is problematic. + // Just remove the attribute instead. + goog.a11y.aria.removeState(element, goog.a11y.aria.State.ACTIVEDESCENDANT); +}; + + +/** + * Handles OPEN events dispatched by items in the container when they are + * opened. + * @param {goog.events.Event} e Open event to handle. + */ +goog.ui.Container.prototype.handleOpenItem = function(e) { + var item = /** @type {goog.ui.Control} */ (e.target); + if (item && item != this.openItem_ && item.getParent() == this) { + if (this.openItem_) { + this.openItem_.setOpen(false); + } + this.openItem_ = item; + } +}; + + +/** + * Handles CLOSE events dispatched by items in the container when they are + * closed. + * @param {goog.events.Event} e Close event to handle. + */ +goog.ui.Container.prototype.handleCloseItem = function(e) { + if (e.target == this.openItem_) { + this.openItem_ = null; + } +}; + + +/** + * Handles mousedown events over the container. The default implementation + * sets the "mouse button pressed" flag and, if the container is focusable, + * grabs keyboard focus. + * @param {goog.events.BrowserEvent} e Mousedown event to handle. + */ +goog.ui.Container.prototype.handleMouseDown = function(e) { + if (this.enabled_) { + this.setMouseButtonPressed(true); + } + + var keyTarget = this.getKeyEventTarget(); + if (keyTarget && goog.dom.isFocusableTabIndex(keyTarget)) { + // The container is configured to receive keyboard focus. + keyTarget.focus(); + } else { + // The control isn't configured to receive keyboard focus; prevent it + // from stealing focus or destroying the selection. + e.preventDefault(); + } +}; + + +/** + * Handles mouseup events over the document. The default implementation + * clears the "mouse button pressed" flag. + * @param {goog.events.BrowserEvent} e Mouseup event to handle. + */ +goog.ui.Container.prototype.handleDocumentMouseUp = function(e) { + this.setMouseButtonPressed(false); +}; + + +/** + * Handles mouse events originating from nodes belonging to the controls hosted + * in the container. Locates the child control based on the DOM node that + * dispatched the event, and forwards the event to the control for handling. + * @param {goog.events.BrowserEvent} e Mouse event to handle. + */ +goog.ui.Container.prototype.handleChildMouseEvents = function(e) { + var control = this.getOwnerControl(/** @type {Node} */ (e.target)); + if (control) { + // Child control identified; forward the event. + switch (e.type) { + case goog.events.EventType.MOUSEDOWN: + control.handleMouseDown(e); + break; + case goog.events.EventType.MOUSEUP: + control.handleMouseUp(e); + break; + case goog.events.EventType.MOUSEOVER: + control.handleMouseOver(e); + break; + case goog.events.EventType.MOUSEOUT: + control.handleMouseOut(e); + break; + case goog.events.EventType.CONTEXTMENU: + control.handleContextMenu(e); + break; + } + } +}; + + +/** + * Returns the child control that owns the given DOM node, or null if no such + * control is found. + * @param {Node} node DOM node whose owner is to be returned. + * @return {goog.ui.Control?} Control hosted in the container to which the node + * belongs (if found). + * @protected + */ +goog.ui.Container.prototype.getOwnerControl = function(node) { + // Ensure that this container actually has child controls before + // looking up the owner. + if (this.childElementIdMap_) { + var elem = this.getElement(); + // See http://b/2964418 . IE9 appears to evaluate '!=' incorrectly, so + // using '!==' instead. + // TODO(user): Possibly revert this change if/when IE9 fixes the issue. + while (node && node !== elem) { + var id = node.id; + if (id in this.childElementIdMap_) { + return this.childElementIdMap_[id]; + } + node = node.parentNode; + } + } + return null; +}; + + +/** + * Handles focus events raised when the container's key event target receives + * keyboard focus. + * @param {goog.events.BrowserEvent} e Focus event to handle. + */ +goog.ui.Container.prototype.handleFocus = function(e) { + // No-op in the base class. +}; + + +/** + * Handles blur events raised when the container's key event target loses + * keyboard focus. The default implementation clears the highlight index. + * @param {goog.events.BrowserEvent} e Blur event to handle. + */ +goog.ui.Container.prototype.handleBlur = function(e) { + this.setHighlightedIndex(-1); + this.setMouseButtonPressed(false); + // If the container loses focus, and one of its children is open, close it. + if (this.openItem_) { + this.openItem_.setOpen(false); + } +}; + + +/** + * Attempts to handle a keyboard event, if the control is enabled, by calling + * {@link handleKeyEventInternal}. Considered protected; should only be used + * within this package and by subclasses. + * @param {goog.events.KeyEvent} e Key event to handle. + * @return {boolean} Whether the key event was handled. + */ +goog.ui.Container.prototype.handleKeyEvent = function(e) { + if (this.isEnabled() && this.isVisible() && + (this.getChildCount() != 0 || this.keyEventTarget_) && + this.handleKeyEventInternal(e)) { + e.preventDefault(); + e.stopPropagation(); + return true; + } + return false; +}; + + +/** + * Attempts to handle a keyboard event; returns true if the event was handled, + * false otherwise. If the container is enabled, and a child is highlighted, + * calls the child control's {@code handleKeyEvent} method to give the control + * a chance to handle the event first. + * @param {goog.events.KeyEvent} e Key event to handle. + * @return {boolean} Whether the event was handled by the container (or one of + * its children). + */ +goog.ui.Container.prototype.handleKeyEventInternal = function(e) { + // Give the highlighted control the chance to handle the key event. + var highlighted = this.getHighlighted(); + if (highlighted && typeof highlighted.handleKeyEvent == 'function' && + highlighted.handleKeyEvent(e)) { + return true; + } + + // Give the open control the chance to handle the key event. + if (this.openItem_ && this.openItem_ != highlighted && + typeof this.openItem_.handleKeyEvent == 'function' && + this.openItem_.handleKeyEvent(e)) { + return true; + } + + // Do not handle the key event if any modifier key is pressed. + if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) { + return false; + } + + // Either nothing is highlighted, or the highlighted control didn't handle + // the key event, so attempt to handle it here. + switch (e.keyCode) { + case goog.events.KeyCodes.ESC: + if (this.isFocusable()) { + this.getKeyEventTarget().blur(); + } else { + return false; + } + break; + + case goog.events.KeyCodes.HOME: + this.highlightFirst(); + break; + + case goog.events.KeyCodes.END: + this.highlightLast(); + break; + + case goog.events.KeyCodes.UP: + if (this.orientation_ == goog.ui.Container.Orientation.VERTICAL) { + this.highlightPrevious(); + } else { + return false; + } + break; + + case goog.events.KeyCodes.LEFT: + if (this.orientation_ == goog.ui.Container.Orientation.HORIZONTAL) { + if (this.isRightToLeft()) { + this.highlightNext(); + } else { + this.highlightPrevious(); + } + } else { + return false; + } + break; + + case goog.events.KeyCodes.DOWN: + if (this.orientation_ == goog.ui.Container.Orientation.VERTICAL) { + this.highlightNext(); + } else { + return false; + } + break; + + case goog.events.KeyCodes.RIGHT: + if (this.orientation_ == goog.ui.Container.Orientation.HORIZONTAL) { + if (this.isRightToLeft()) { + this.highlightPrevious(); + } else { + this.highlightNext(); + } + } else { + return false; + } + break; + + default: + return false; + } + + return true; +}; + + +// Child component management. + + +/** + * Creates a DOM ID for the child control and registers it to an internal + * hash table to be able to find it fast by id. + * @param {goog.ui.Component} child The child control. Its root element has + * to be created yet. + * @private + */ +goog.ui.Container.prototype.registerChildId_ = function(child) { + // Map the DOM ID of the control's root element to the control itself. + var childElem = child.getElement(); + + // If the control's root element doesn't have a DOM ID assign one. + var id = childElem.id || (childElem.id = child.getId()); + + // Lazily create the child element ID map on first use. + if (!this.childElementIdMap_) { + this.childElementIdMap_ = {}; + } + this.childElementIdMap_[id] = child; +}; + + +/** + * Adds the specified control as the last child of this container. See + * {@link goog.ui.Container#addChildAt} for detailed semantics. + * @param {goog.ui.Component} child The new child control. + * @param {boolean=} opt_render Whether the new child should be rendered + * immediately after being added (defaults to false). + * @override + */ +goog.ui.Container.prototype.addChild = function(child, opt_render) { + goog.asserts.assertInstanceof(child, goog.ui.Control, + 'The child of a container must be a control'); + goog.ui.Container.superClass_.addChild.call(this, child, opt_render); +}; + + +/** + * Overrides {@link goog.ui.Container#getChild} to make it clear that it + * only returns {@link goog.ui.Control}s. + * @param {string} id Child component ID. + * @return {goog.ui.Control} The child with the given ID; null if none. + * @override + */ +goog.ui.Container.prototype.getChild; + + +/** + * Overrides {@link goog.ui.Container#getChildAt} to make it clear that it + * only returns {@link goog.ui.Control}s. + * @param {number} index 0-based index. + * @return {goog.ui.Control} The child with the given ID; null if none. + * @override + */ +goog.ui.Container.prototype.getChildAt; + + +/** + * Adds the control as a child of this container at the given 0-based index. + * Overrides {@link goog.ui.Component#addChildAt} by also updating the + * container's highlight index. Since {@link goog.ui.Component#addChild} uses + * {@link #addChildAt} internally, we only need to override this method. + * @param {goog.ui.Component} control New child. + * @param {number} index Index at which the new child is to be added. + * @param {boolean=} opt_render Whether the new child should be rendered + * immediately after being added (defaults to false). + * @override + */ +goog.ui.Container.prototype.addChildAt = function(control, index, opt_render) { + // Make sure the child control dispatches HIGHLIGHT, UNHIGHLIGHT, OPEN, and + // CLOSE events, and that it doesn't steal keyboard focus. + control.setDispatchTransitionEvents(goog.ui.Component.State.HOVER, true); + control.setDispatchTransitionEvents(goog.ui.Component.State.OPENED, true); + if (this.isFocusable() || !this.isFocusableChildrenAllowed()) { + control.setSupportedState(goog.ui.Component.State.FOCUSED, false); + } + + // Disable mouse event handling by child controls. + control.setHandleMouseEvents(false); + + // Let the superclass implementation do the work. + goog.ui.Container.superClass_.addChildAt.call(this, control, index, + opt_render); + + if (control.isInDocument() && this.isInDocument()) { + this.registerChildId_(control); + } + + // Update the highlight index, if needed. + if (index <= this.highlightedIndex_) { + this.highlightedIndex_++; + } +}; + + +/** + * Removes a child control. Overrides {@link goog.ui.Component#removeChild} by + * updating the highlight index. Since {@link goog.ui.Component#removeChildAt} + * uses {@link #removeChild} internally, we only need to override this method. + * @param {string|goog.ui.Component} control The ID of the child to remove, or + * the control itself. + * @param {boolean=} opt_unrender Whether to call {@code exitDocument} on the + * removed control, and detach its DOM from the document (defaults to + * false). + * @return {goog.ui.Control} The removed control, if any. + * @override + */ +goog.ui.Container.prototype.removeChild = function(control, opt_unrender) { + control = goog.isString(control) ? this.getChild(control) : control; + + if (control) { + var index = this.indexOfChild(control); + if (index != -1) { + if (index == this.highlightedIndex_) { + control.setHighlighted(false); + this.highlightedIndex_ = -1; + } else if (index < this.highlightedIndex_) { + this.highlightedIndex_--; + } + } + + // Remove the mapping from the child element ID map. + var childElem = control.getElement(); + if (childElem && childElem.id && this.childElementIdMap_) { + goog.object.remove(this.childElementIdMap_, childElem.id); + } + } + + control = /** @type {goog.ui.Control} */ ( + goog.ui.Container.superClass_.removeChild.call(this, control, + opt_unrender)); + + // Re-enable mouse event handling (in case the control is reused elsewhere). + control.setHandleMouseEvents(true); + + return control; +}; + + +// Container state management. + + +/** + * Returns the container's orientation. + * @return {?goog.ui.Container.Orientation} Container orientation. + */ +goog.ui.Container.prototype.getOrientation = function() { + return this.orientation_; +}; + + +/** + * Sets the container's orientation. + * @param {goog.ui.Container.Orientation} orientation Container orientation. + */ +// TODO(attila): Do we need to support containers with dynamic orientation? +goog.ui.Container.prototype.setOrientation = function(orientation) { + if (this.getElement()) { + // Too late. + throw Error(goog.ui.Component.Error.ALREADY_RENDERED); + } + + this.orientation_ = orientation; +}; + + +/** + * Returns true if the container's visibility is set to visible, false if + * it is set to hidden. A container that is set to hidden is guaranteed + * to be hidden from the user, but the reverse isn't necessarily true. + * A container may be set to visible but can otherwise be obscured by another + * element, rendered off-screen, or hidden using direct CSS manipulation. + * @return {boolean} Whether the container is set to be visible. + */ +goog.ui.Container.prototype.isVisible = function() { + return this.visible_; +}; + + +/** + * Shows or hides the container. Does nothing if the container already has + * the requested visibility. Otherwise, dispatches a SHOW or HIDE event as + * appropriate, giving listeners a chance to prevent the visibility change. + * @param {boolean} visible Whether to show or hide the container. + * @param {boolean=} opt_force If true, doesn't check whether the container + * already has the requested visibility, and doesn't dispatch any events. + * @return {boolean} Whether the visibility was changed. + */ +goog.ui.Container.prototype.setVisible = function(visible, opt_force) { + if (opt_force || (this.visible_ != visible && this.dispatchEvent(visible ? + goog.ui.Component.EventType.SHOW : goog.ui.Component.EventType.HIDE))) { + this.visible_ = visible; + + var elem = this.getElement(); + if (elem) { + goog.style.setElementShown(elem, visible); + if (this.isFocusable()) { + // Enable keyboard access only for enabled & visible containers. + this.renderer_.enableTabIndex(this.getKeyEventTarget(), + this.enabled_ && this.visible_); + } + if (!opt_force) { + this.dispatchEvent(this.visible_ ? + goog.ui.Container.EventType.AFTER_SHOW : + goog.ui.Container.EventType.AFTER_HIDE); + } + } + + return true; + } + + return false; +}; + + +/** + * Returns true if the container is enabled, false otherwise. + * @return {boolean} Whether the container is enabled. + */ +goog.ui.Container.prototype.isEnabled = function() { + return this.enabled_; +}; + + +/** + * Enables/disables the container based on the {@code enable} argument. + * Dispatches an {@code ENABLED} or {@code DISABLED} event prior to changing + * the container's state, which may be caught and canceled to prevent the + * container from changing state. Also enables/disables child controls. + * @param {boolean} enable Whether to enable or disable the container. + */ +goog.ui.Container.prototype.setEnabled = function(enable) { + if (this.enabled_ != enable && this.dispatchEvent(enable ? + goog.ui.Component.EventType.ENABLE : + goog.ui.Component.EventType.DISABLE)) { + if (enable) { + // Flag the container as enabled first, then update children. This is + // because controls can't be enabled if their parent is disabled. + this.enabled_ = true; + this.forEachChild(function(child) { + // Enable child control unless it is flagged. + if (child.wasDisabled) { + delete child.wasDisabled; + } else { + child.setEnabled(true); + } + }); + } else { + // Disable children first, then flag the container as disabled. This is + // because controls can't be disabled if their parent is already disabled. + this.forEachChild(function(child) { + // Disable child control, or flag it if it's already disabled. + if (child.isEnabled()) { + child.setEnabled(false); + } else { + child.wasDisabled = true; + } + }); + this.enabled_ = false; + this.setMouseButtonPressed(false); + } + + if (this.isFocusable()) { + // Enable keyboard access only for enabled & visible components. + this.renderer_.enableTabIndex(this.getKeyEventTarget(), + enable && this.visible_); + } + } +}; + + +/** + * Returns true if the container is focusable, false otherwise. The default + * is true. Focusable containers always have a tab index and allocate a key + * handler to handle keyboard events while focused. + * @return {boolean} Whether the component is focusable. + */ +goog.ui.Container.prototype.isFocusable = function() { + return this.focusable_; +}; + + +/** + * Sets whether the container is focusable. The default is true. Focusable + * containers always have a tab index and allocate a key handler to handle + * keyboard events while focused. + * @param {boolean} focusable Whether the component is to be focusable. + */ +goog.ui.Container.prototype.setFocusable = function(focusable) { + if (focusable != this.focusable_ && this.isInDocument()) { + this.enableFocusHandling_(focusable); + } + this.focusable_ = focusable; + if (this.enabled_ && this.visible_) { + this.renderer_.enableTabIndex(this.getKeyEventTarget(), focusable); + } +}; + + +/** + * Returns true if the container allows children to be focusable, false + * otherwise. Only effective if the container is not focusable. + * @return {boolean} Whether children should be focusable. + */ +goog.ui.Container.prototype.isFocusableChildrenAllowed = function() { + return this.allowFocusableChildren_; +}; + + +/** + * Sets whether the container allows children to be focusable, false + * otherwise. Only effective if the container is not focusable. + * @param {boolean} focusable Whether the children should be focusable. + */ +goog.ui.Container.prototype.setFocusableChildrenAllowed = function(focusable) { + this.allowFocusableChildren_ = focusable; +}; + + +/** + * @return {boolean} Whether highlighting a child component should also open it. + */ +goog.ui.Container.prototype.isOpenFollowsHighlight = function() { + return this.openFollowsHighlight_; +}; + + +/** + * Sets whether highlighting a child component should also open it. + * @param {boolean} follow Whether highlighting a child component also opens it. + */ +goog.ui.Container.prototype.setOpenFollowsHighlight = function(follow) { + this.openFollowsHighlight_ = follow; +}; + + +// Highlight management. + + +/** + * Returns the index of the currently highlighted item (-1 if none). + * @return {number} Index of the currently highlighted item. + */ +goog.ui.Container.prototype.getHighlightedIndex = function() { + return this.highlightedIndex_; +}; + + +/** + * Highlights the item at the given 0-based index (if any). If another item + * was previously highlighted, it is un-highlighted. + * @param {number} index Index of item to highlight (-1 removes the current + * highlight). + */ +goog.ui.Container.prototype.setHighlightedIndex = function(index) { + var child = this.getChildAt(index); + if (child) { + child.setHighlighted(true); + } else if (this.highlightedIndex_ > -1) { + this.getHighlighted().setHighlighted(false); + } +}; + + +/** + * Highlights the given item if it exists and is a child of the container; + * otherwise un-highlights the currently highlighted item. + * @param {goog.ui.Control} item Item to highlight. + */ +goog.ui.Container.prototype.setHighlighted = function(item) { + this.setHighlightedIndex(this.indexOfChild(item)); +}; + + +/** + * Returns the currently highlighted item (if any). + * @return {goog.ui.Control?} Highlighted item (null if none). + */ +goog.ui.Container.prototype.getHighlighted = function() { + return this.getChildAt(this.highlightedIndex_); +}; + + +/** + * Highlights the first highlightable item in the container + */ +goog.ui.Container.prototype.highlightFirst = function() { + this.highlightHelper(function(index, max) { + return (index + 1) % max; + }, this.getChildCount() - 1); +}; + + +/** + * Highlights the last highlightable item in the container. + */ +goog.ui.Container.prototype.highlightLast = function() { + this.highlightHelper(function(index, max) { + index--; + return index < 0 ? max - 1 : index; + }, 0); +}; + + +/** + * Highlights the next highlightable item (or the first if nothing is currently + * highlighted). + */ +goog.ui.Container.prototype.highlightNext = function() { + this.highlightHelper(function(index, max) { + return (index + 1) % max; + }, this.highlightedIndex_); +}; + + +/** + * Highlights the previous highlightable item (or the last if nothing is + * currently highlighted). + */ +goog.ui.Container.prototype.highlightPrevious = function() { + this.highlightHelper(function(index, max) { + index--; + return index < 0 ? max - 1 : index; + }, this.highlightedIndex_); +}; + + +/** + * Helper function that manages the details of moving the highlight among + * child controls in response to keyboard events. + * @param {function(number, number) : number} fn Function that accepts the + * current and maximum indices, and returns the next index to check. + * @param {number} startIndex Start index. + * @return {boolean} Whether the highlight has changed. + * @protected + */ +goog.ui.Container.prototype.highlightHelper = function(fn, startIndex) { + // If the start index is -1 (meaning there's nothing currently highlighted), + // try starting from the currently open item, if any. + var curIndex = startIndex < 0 ? + this.indexOfChild(this.openItem_) : startIndex; + var numItems = this.getChildCount(); + + curIndex = fn.call(this, curIndex, numItems); + var visited = 0; + while (visited <= numItems) { + var control = this.getChildAt(curIndex); + if (control && this.canHighlightItem(control)) { + this.setHighlightedIndexFromKeyEvent(curIndex); + return true; + } + visited++; + curIndex = fn.call(this, curIndex, numItems); + } + return false; +}; + + +/** + * Returns whether the given item can be highlighted. + * @param {goog.ui.Control} item The item to check. + * @return {boolean} Whether the item can be highlighted. + * @protected + */ +goog.ui.Container.prototype.canHighlightItem = function(item) { + return item.isVisible() && item.isEnabled() && + item.isSupportedState(goog.ui.Component.State.HOVER); +}; + + +/** + * Helper method that sets the highlighted index to the given index in response + * to a keyboard event. The base class implementation simply calls the + * {@link #setHighlightedIndex} method, but subclasses can override this + * behavior as needed. + * @param {number} index Index of item to highlight. + * @protected + */ +goog.ui.Container.prototype.setHighlightedIndexFromKeyEvent = function(index) { + this.setHighlightedIndex(index); +}; + + +/** + * Returns the currently open (expanded) control in the container (null if + * none). + * @return {goog.ui.Control?} The currently open control. + */ +goog.ui.Container.prototype.getOpenItem = function() { + return this.openItem_; +}; + + +/** + * Returns true if the mouse button is pressed, false otherwise. + * @return {boolean} Whether the mouse button is pressed. + */ +goog.ui.Container.prototype.isMouseButtonPressed = function() { + return this.mouseButtonPressed_; +}; + + +/** + * Sets or clears the "mouse button pressed" flag. + * @param {boolean} pressed Whether the mouse button is presed. + */ +goog.ui.Container.prototype.setMouseButtonPressed = function(pressed) { + this.mouseButtonPressed_ = pressed; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/containerrenderer.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/containerrenderer.js new file mode 100644 index 0000000..9ea3b36 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/containerrenderer.js @@ -0,0 +1,374 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Base class for container renderers. + * + * @author attila@google.com (Attila Bodis) + */ + +goog.provide('goog.ui.ContainerRenderer'); + +goog.require('goog.a11y.aria'); +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom.NodeType'); +goog.require('goog.dom.classlist'); +goog.require('goog.string'); +goog.require('goog.style'); +goog.require('goog.ui.registry'); +goog.require('goog.userAgent'); + + + +/** + * Default renderer for {@link goog.ui.Container}. Can be used as-is, but + * subclasses of Container will probably want to use renderers specifically + * tailored for them by extending this class. + * @param {string=} opt_ariaRole Optional ARIA role used for the element. + * @constructor + */ +goog.ui.ContainerRenderer = function(opt_ariaRole) { + // By default, the ARIA role is unspecified. + /** @private {string|undefined} */ + this.ariaRole_ = opt_ariaRole; +}; +goog.addSingletonGetter(goog.ui.ContainerRenderer); + + +/** + * Constructs a new renderer and sets the CSS class that the renderer will use + * as the base CSS class to apply to all elements rendered by that renderer. + * An example to use this function using a menu is: + * + * <pre> + * var myCustomRenderer = goog.ui.ContainerRenderer.getCustomRenderer( + * goog.ui.MenuRenderer, 'my-special-menu'); + * var newMenu = new goog.ui.Menu(opt_domHelper, myCustomRenderer); + * </pre> + * + * Your styles for the menu can now be: + * <pre> + * .my-special-menu { } + * </pre> + * + * <em>instead</em> of + * <pre> + * .CSS_MY_SPECIAL_MENU .goog-menu { } + * </pre> + * + * You would want to use this functionality when you want an instance of a + * component to have specific styles different than the other components of the + * same type in your application. This avoids using descendant selectors to + * apply the specific styles to this component. + * + * @param {Function} ctor The constructor of the renderer you want to create. + * @param {string} cssClassName The name of the CSS class for this renderer. + * @return {goog.ui.ContainerRenderer} An instance of the desired renderer with + * its getCssClass() method overridden to return the supplied custom CSS + * class name. + */ +goog.ui.ContainerRenderer.getCustomRenderer = function(ctor, cssClassName) { + var renderer = new ctor(); + + /** + * Returns the CSS class to be applied to the root element of components + * rendered using this renderer. + * @return {string} Renderer-specific CSS class. + */ + renderer.getCssClass = function() { + return cssClassName; + }; + + return renderer; +}; + + +/** + * Default CSS class to be applied to the root element of containers rendered + * by this renderer. + * @type {string} + */ +goog.ui.ContainerRenderer.CSS_CLASS = goog.getCssName('goog-container'); + + +/** + * Returns the ARIA role to be applied to the container. + * See http://wiki/Main/ARIA for more info. + * @return {undefined|string} ARIA role. + */ +goog.ui.ContainerRenderer.prototype.getAriaRole = function() { + return this.ariaRole_; +}; + + +/** + * Enables or disables the tab index of the element. Only elements with a + * valid tab index can receive focus. + * @param {Element} element Element whose tab index is to be changed. + * @param {boolean} enable Whether to add or remove the element's tab index. + */ +goog.ui.ContainerRenderer.prototype.enableTabIndex = function(element, enable) { + if (element) { + element.tabIndex = enable ? 0 : -1; + } +}; + + +/** + * Creates and returns the container's root element. The default + * simply creates a DIV and applies the renderer's own CSS class name to it. + * To be overridden in subclasses. + * @param {goog.ui.Container} container Container to render. + * @return {Element} Root element for the container. + */ +goog.ui.ContainerRenderer.prototype.createDom = function(container) { + return container.getDomHelper().createDom('div', + this.getClassNames(container).join(' ')); +}; + + +/** + * Returns the DOM element into which child components are to be rendered, + * or null if the container hasn't been rendered yet. + * @param {Element} element Root element of the container whose content element + * is to be returned. + * @return {Element} Element to contain child elements (null if none). + */ +goog.ui.ContainerRenderer.prototype.getContentElement = function(element) { + return element; +}; + + +/** + * Default implementation of {@code canDecorate}; returns true if the element + * is a DIV, false otherwise. + * @param {Element} element Element to decorate. + * @return {boolean} Whether the renderer can decorate the element. + */ +goog.ui.ContainerRenderer.prototype.canDecorate = function(element) { + return element.tagName == 'DIV'; +}; + + +/** + * Default implementation of {@code decorate} for {@link goog.ui.Container}s. + * Decorates the element with the container, and attempts to decorate its child + * elements. Returns the decorated element. + * @param {goog.ui.Container} container Container to decorate the element. + * @param {Element} element Element to decorate. + * @return {!Element} Decorated element. + */ +goog.ui.ContainerRenderer.prototype.decorate = function(container, element) { + // Set the container's ID to the decorated element's DOM ID, if any. + if (element.id) { + container.setId(element.id); + } + + // Configure the container's state based on the CSS class names it has. + var baseClass = this.getCssClass(); + var hasBaseClass = false; + var classNames = goog.dom.classlist.get(element); + if (classNames) { + goog.array.forEach(classNames, function(className) { + if (className == baseClass) { + hasBaseClass = true; + } else if (className) { + this.setStateFromClassName(container, className, baseClass); + } + }, this); + } + + if (!hasBaseClass) { + // Make sure the container's root element has the renderer's own CSS class. + goog.dom.classlist.add(element, baseClass); + } + + // Decorate the element's children, if applicable. This should happen after + // the container's own state has been initialized, since how children are + // decorated may depend on the state of the container. + this.decorateChildren(container, this.getContentElement(element)); + + return element; +}; + + +/** + * Sets the container's state based on the given CSS class name, encountered + * during decoration. CSS class names that don't represent container states + * are ignored. Considered protected; subclasses should override this method + * to support more states and CSS class names. + * @param {goog.ui.Container} container Container to update. + * @param {string} className CSS class name. + * @param {string} baseClass Base class name used as the root of state-specific + * class names (typically the renderer's own class name). + * @protected + */ +goog.ui.ContainerRenderer.prototype.setStateFromClassName = function(container, + className, baseClass) { + if (className == goog.getCssName(baseClass, 'disabled')) { + container.setEnabled(false); + } else if (className == goog.getCssName(baseClass, 'horizontal')) { + container.setOrientation(goog.ui.Container.Orientation.HORIZONTAL); + } else if (className == goog.getCssName(baseClass, 'vertical')) { + container.setOrientation(goog.ui.Container.Orientation.VERTICAL); + } +}; + + +/** + * Takes a container and an element that may contain child elements, decorates + * the child elements, and adds the corresponding components to the container + * as child components. Any non-element child nodes (e.g. empty text nodes + * introduced by line breaks in the HTML source) are removed from the element. + * @param {goog.ui.Container} container Container whose children are to be + * discovered. + * @param {Element} element Element whose children are to be decorated. + * @param {Element=} opt_firstChild the first child to be decorated. + */ +goog.ui.ContainerRenderer.prototype.decorateChildren = function(container, + element, opt_firstChild) { + if (element) { + var node = opt_firstChild || element.firstChild, next; + // Tag soup HTML may result in a DOM where siblings have different parents. + while (node && node.parentNode == element) { + // Get the next sibling here, since the node may be replaced or removed. + next = node.nextSibling; + if (node.nodeType == goog.dom.NodeType.ELEMENT) { + // Decorate element node. + var child = this.getDecoratorForChild(/** @type {Element} */(node)); + if (child) { + // addChild() may need to look at the element. + child.setElementInternal(/** @type {Element} */(node)); + // If the container is disabled, mark the child disabled too. See + // bug 1263729. Note that this must precede the call to addChild(). + if (!container.isEnabled()) { + child.setEnabled(false); + } + container.addChild(child); + child.decorate(/** @type {Element} */(node)); + } + } else if (!node.nodeValue || goog.string.trim(node.nodeValue) == '') { + // Remove empty text node, otherwise madness ensues (e.g. controls that + // use goog-inline-block will flicker and shift on hover on Gecko). + element.removeChild(node); + } + node = next; + } + } +}; + + +/** + * Inspects the element, and creates an instance of {@link goog.ui.Control} or + * an appropriate subclass best suited to decorate it. Returns the control (or + * null if no suitable class was found). This default implementation uses the + * element's CSS class to find the appropriate control class to instantiate. + * May be overridden in subclasses. + * @param {Element} element Element to decorate. + * @return {goog.ui.Control?} A new control suitable to decorate the element + * (null if none). + */ +goog.ui.ContainerRenderer.prototype.getDecoratorForChild = function(element) { + return /** @type {goog.ui.Control} */ ( + goog.ui.registry.getDecorator(element)); +}; + + +/** + * Initializes the container's DOM when the container enters the document. + * Called from {@link goog.ui.Container#enterDocument}. + * @param {goog.ui.Container} container Container whose DOM is to be initialized + * as it enters the document. + */ +goog.ui.ContainerRenderer.prototype.initializeDom = function(container) { + var elem = container.getElement(); + goog.asserts.assert(elem, 'The container DOM element cannot be null.'); + // Make sure the container's element isn't selectable. On Gecko, recursively + // marking each child element unselectable is expensive and unnecessary, so + // only mark the root element unselectable. + goog.style.setUnselectable(elem, true, goog.userAgent.GECKO); + + // IE doesn't support outline:none, so we have to use the hideFocus property. + if (goog.userAgent.IE) { + elem.hideFocus = true; + } + + // Set the ARIA role. + var ariaRole = this.getAriaRole(); + if (ariaRole) { + goog.a11y.aria.setRole(elem, ariaRole); + } +}; + + +/** + * Returns the element within the container's DOM that should receive keyboard + * focus (null if none). The default implementation returns the container's + * root element. + * @param {goog.ui.Container} container Container whose key event target is + * to be returned. + * @return {Element} Key event target (null if none). + */ +goog.ui.ContainerRenderer.prototype.getKeyEventTarget = function(container) { + return container.getElement(); +}; + + +/** + * Returns the CSS class to be applied to the root element of containers + * rendered using this renderer. + * @return {string} Renderer-specific CSS class. + */ +goog.ui.ContainerRenderer.prototype.getCssClass = function() { + return goog.ui.ContainerRenderer.CSS_CLASS; +}; + + +/** + * Returns all CSS class names applicable to the given container, based on its + * state. The array of class names returned includes the renderer's own CSS + * class, followed by a CSS class indicating the container's orientation, + * followed by any state-specific CSS classes. + * @param {goog.ui.Container} container Container whose CSS classes are to be + * returned. + * @return {!Array.<string>} Array of CSS class names applicable to the + * container. + */ +goog.ui.ContainerRenderer.prototype.getClassNames = function(container) { + var baseClass = this.getCssClass(); + var isHorizontal = + container.getOrientation() == goog.ui.Container.Orientation.HORIZONTAL; + var classNames = [ + baseClass, + (isHorizontal ? + goog.getCssName(baseClass, 'horizontal') : + goog.getCssName(baseClass, 'vertical')) + ]; + if (!container.isEnabled()) { + classNames.push(goog.getCssName(baseClass, 'disabled')); + } + return classNames; +}; + + +/** + * Returns the default orientation of containers rendered or decorated by this + * renderer. The base class implementation returns {@code VERTICAL}. + * @return {goog.ui.Container.Orientation} Default orientation for containers + * created or decorated by this renderer. + */ +goog.ui.ContainerRenderer.prototype.getDefaultOrientation = function() { + return goog.ui.Container.Orientation.VERTICAL; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/control.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/control.js new file mode 100644 index 0000000..a258c26 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/control.js @@ -0,0 +1,1388 @@ +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Base class for UI controls such as buttons, menus, menu items, + * toolbar buttons, etc. The implementation is based on a generalized version + * of {@link goog.ui.MenuItem}. + * TODO(attila): If the renderer framework works well, pull it into Component. + * + * @author attila@google.com (Attila Bodis) + * @see ../demos/control.html + * @see http://code.google.com/p/closure-library/wiki/IntroToControls + */ + +goog.provide('goog.ui.Control'); + +goog.require('goog.array'); +goog.require('goog.dom'); +goog.require('goog.events.Event'); +goog.require('goog.events.EventType'); +goog.require('goog.events.KeyCodes'); +goog.require('goog.events.KeyHandler'); +goog.require('goog.string'); +goog.require('goog.ui.Component'); +/** @suppress {extraRequire} */ +goog.require('goog.ui.ControlContent'); +goog.require('goog.ui.ControlRenderer'); +goog.require('goog.ui.decorate'); +goog.require('goog.ui.registry'); +goog.require('goog.userAgent'); + + + +/** + * Base class for UI controls. Extends {@link goog.ui.Component} by adding + * the following: + * <ul> + * <li>a {@link goog.events.KeyHandler}, to simplify keyboard handling, + * <li>a pluggable <em>renderer</em> framework, to simplify the creation of + * simple controls without the need to subclass this class, + * <li>the notion of component <em>content</em>, like a text caption or DOM + * structure displayed in the component (e.g. a button label), + * <li>getter and setter for component content, as well as a getter and + * setter specifically for caption text (for convenience), + * <li>support for hiding/showing the component, + <li>fine-grained control over supported states and state transition + events, and + * <li>default mouse and keyboard event handling. + * </ul> + * This class has sufficient built-in functionality for most simple UI controls. + * All controls dispatch SHOW, HIDE, ENTER, LEAVE, and ACTION events on show, + * hide, mouseover, mouseout, and user action, respectively. Additional states + * are also supported. See closure/demos/control.html + * for example usage. + * @param {goog.ui.ControlContent=} opt_content Text caption or DOM structure + * to display as the content of the control (if any). + * @param {goog.ui.ControlRenderer=} opt_renderer Renderer used to render or + * decorate the component; defaults to {@link goog.ui.ControlRenderer}. + * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, used for + * document interaction. + * @constructor + * @extends {goog.ui.Component} + */ +goog.ui.Control = function(opt_content, opt_renderer, opt_domHelper) { + goog.ui.Component.call(this, opt_domHelper); + this.renderer_ = opt_renderer || + goog.ui.registry.getDefaultRenderer(this.constructor); + this.setContentInternal(goog.isDef(opt_content) ? opt_content : null); +}; +goog.inherits(goog.ui.Control, goog.ui.Component); +goog.tagUnsealableClass(goog.ui.Control); + + +// Renderer registry. +// TODO(attila): Refactor existing usages inside Google in a follow-up CL. + + +/** + * Maps a CSS class name to a function that returns a new instance of + * {@link goog.ui.Control} or a subclass thereof, suitable to decorate + * an element that has the specified CSS class. UI components that extend + * {@link goog.ui.Control} and want {@link goog.ui.Container}s to be able + * to discover and decorate elements using them should register a factory + * function via this API. + * @param {string} className CSS class name. + * @param {Function} decoratorFunction Function that takes no arguments and + * returns a new instance of a control to decorate an element with the + * given class. + * @deprecated Use {@link goog.ui.registry.setDecoratorByClassName} instead. + */ +goog.ui.Control.registerDecorator = goog.ui.registry.setDecoratorByClassName; + + +/** + * Takes an element and returns a new instance of {@link goog.ui.Control} + * or a subclass, suitable to decorate it (based on the element's CSS class). + * @param {Element} element Element to decorate. + * @return {goog.ui.Control?} New control instance to decorate the element + * (null if none). + * @deprecated Use {@link goog.ui.registry.getDecorator} instead. + */ +goog.ui.Control.getDecorator = + /** @type {function(Element): goog.ui.Control} */ ( + goog.ui.registry.getDecorator); + + +/** + * Takes an element, and decorates it with a {@link goog.ui.Control} instance + * if a suitable decorator is found. + * @param {Element} element Element to decorate. + * @return {goog.ui.Control?} New control instance that decorates the element + * (null if none). + * @deprecated Use {@link goog.ui.decorate} instead. + */ +goog.ui.Control.decorate = /** @type {function(Element): goog.ui.Control} */ ( + goog.ui.decorate); + + +/** + * Renderer associated with the component. + * @type {goog.ui.ControlRenderer|undefined} + * @private + */ +goog.ui.Control.prototype.renderer_; + + +/** + * Text caption or DOM structure displayed in the component. + * @type {goog.ui.ControlContent} + * @private + */ +goog.ui.Control.prototype.content_ = null; + + +/** + * Current component state; a bit mask of {@link goog.ui.Component.State}s. + * @type {number} + * @private + */ +goog.ui.Control.prototype.state_ = 0x00; + + +/** + * A bit mask of {@link goog.ui.Component.State}s this component supports. + * @type {number} + * @private + */ +goog.ui.Control.prototype.supportedStates_ = + goog.ui.Component.State.DISABLED | + goog.ui.Component.State.HOVER | + goog.ui.Component.State.ACTIVE | + goog.ui.Component.State.FOCUSED; + + +/** + * A bit mask of {@link goog.ui.Component.State}s for which this component + * provides default event handling. For example, a component that handles + * the HOVER state automatically will highlight itself on mouseover, whereas + * a component that doesn't handle HOVER automatically will only dispatch + * ENTER and LEAVE events but not call {@link setHighlighted} on itself. + * By default, components provide default event handling for all states. + * Controls hosted in containers (e.g. menu items in a menu, or buttons in a + * toolbar) will typically want to have their container manage their highlight + * state. Selectable controls managed by a selection model will also typically + * want their selection state to be managed by the model. + * @type {number} + * @private + */ +goog.ui.Control.prototype.autoStates_ = goog.ui.Component.State.ALL; + + +/** + * A bit mask of {@link goog.ui.Component.State}s for which this component + * dispatches state transition events. Because events are expensive, the + * default behavior is to not dispatch any state transition events at all. + * Use the {@link #setDispatchTransitionEvents} API to request transition + * events as needed. Subclasses may enable transition events by default. + * Controls hosted in containers or managed by a selection model will typically + * want to dispatch transition events. + * @type {number} + * @private + */ +goog.ui.Control.prototype.statesWithTransitionEvents_ = 0x00; + + +/** + * Component visibility. + * @type {boolean} + * @private + */ +goog.ui.Control.prototype.visible_ = true; + + +/** + * Keyboard event handler. + * @type {goog.events.KeyHandler} + * @private + */ +goog.ui.Control.prototype.keyHandler_; + + +/** + * Additional class name(s) to apply to the control's root element, if any. + * @type {Array.<string>?} + * @private + */ +goog.ui.Control.prototype.extraClassNames_ = null; + + +/** + * Whether the control should listen for and handle mouse events; defaults to + * true. + * @type {boolean} + * @private + */ +goog.ui.Control.prototype.handleMouseEvents_ = true; + + +/** + * Whether the control allows text selection within its DOM. Defaults to false. + * @type {boolean} + * @private + */ +goog.ui.Control.prototype.allowTextSelection_ = false; + + +/** + * The control's preferred ARIA role. + * @type {?goog.a11y.aria.Role} + * @private + */ +goog.ui.Control.prototype.preferredAriaRole_ = null; + + +// Event handler and renderer management. + + +/** + * Returns true if the control is configured to handle its own mouse events, + * false otherwise. Controls not hosted in {@link goog.ui.Container}s have + * to handle their own mouse events, but controls hosted in containers may + * allow their parent to handle mouse events on their behalf. Considered + * protected; should only be used within this package and by subclasses. + * @return {boolean} Whether the control handles its own mouse events. + */ +goog.ui.Control.prototype.isHandleMouseEvents = function() { + return this.handleMouseEvents_; +}; + + +/** + * Enables or disables mouse event handling for the control. Containers may + * use this method to disable mouse event handling in their child controls. + * Considered protected; should only be used within this package and by + * subclasses. + * @param {boolean} enable Whether to enable or disable mouse event handling. + */ +goog.ui.Control.prototype.setHandleMouseEvents = function(enable) { + if (this.isInDocument() && enable != this.handleMouseEvents_) { + // Already in the document; need to update event handler. + this.enableMouseEventHandling_(enable); + } + this.handleMouseEvents_ = enable; +}; + + +/** + * Returns the DOM element on which the control is listening for keyboard + * events (null if none). + * @return {Element} Element on which the control is listening for key + * events. + */ +goog.ui.Control.prototype.getKeyEventTarget = function() { + // Delegate to renderer. + return this.renderer_.getKeyEventTarget(this); +}; + + +/** + * Returns the keyboard event handler for this component, lazily created the + * first time this method is called. Considered protected; should only be + * used within this package and by subclasses. + * @return {!goog.events.KeyHandler} Keyboard event handler for this component. + * @protected + */ +goog.ui.Control.prototype.getKeyHandler = function() { + return this.keyHandler_ || (this.keyHandler_ = new goog.events.KeyHandler()); +}; + + +/** + * Returns the renderer used by this component to render itself or to decorate + * an existing element. + * @return {goog.ui.ControlRenderer|undefined} Renderer used by the component + * (undefined if none). + */ +goog.ui.Control.prototype.getRenderer = function() { + return this.renderer_; +}; + + +/** + * Registers the given renderer with the component. Changing renderers after + * the component has entered the document is an error. + * @param {goog.ui.ControlRenderer} renderer Renderer used by the component. + * @throws {Error} If the control is already in the document. + */ +goog.ui.Control.prototype.setRenderer = function(renderer) { + if (this.isInDocument()) { + // Too late. + throw Error(goog.ui.Component.Error.ALREADY_RENDERED); + } + + if (this.getElement()) { + // The component has already been rendered, but isn't yet in the document. + // Replace the renderer and delete the current DOM, so it can be re-rendered + // using the new renderer the next time someone calls render(). + this.setElementInternal(null); + } + + this.renderer_ = renderer; +}; + + +// Support for additional styling. + + +/** + * Returns any additional class name(s) to be applied to the component's + * root element, or null if no extra class names are needed. + * @return {Array.<string>?} Additional class names to be applied to + * the component's root element (null if none). + */ +goog.ui.Control.prototype.getExtraClassNames = function() { + return this.extraClassNames_; +}; + + +/** + * Adds the given class name to the list of classes to be applied to the + * component's root element. + * @param {string} className Additional class name to be applied to the + * component's root element. + */ +goog.ui.Control.prototype.addClassName = function(className) { + if (className) { + if (this.extraClassNames_) { + if (!goog.array.contains(this.extraClassNames_, className)) { + this.extraClassNames_.push(className); + } + } else { + this.extraClassNames_ = [className]; + } + this.renderer_.enableExtraClassName(this, className, true); + } +}; + + +/** + * Removes the given class name from the list of classes to be applied to + * the component's root element. + * @param {string} className Class name to be removed from the component's root + * element. + */ +goog.ui.Control.prototype.removeClassName = function(className) { + if (className && this.extraClassNames_ && + goog.array.remove(this.extraClassNames_, className)) { + if (this.extraClassNames_.length == 0) { + this.extraClassNames_ = null; + } + this.renderer_.enableExtraClassName(this, className, false); + } +}; + + +/** + * Adds or removes the given class name to/from the list of classes to be + * applied to the component's root element. + * @param {string} className CSS class name to add or remove. + * @param {boolean} enable Whether to add or remove the class name. + */ +goog.ui.Control.prototype.enableClassName = function(className, enable) { + if (enable) { + this.addClassName(className); + } else { + this.removeClassName(className); + } +}; + + +// Standard goog.ui.Component implementation. + + +/** + * Creates the control's DOM. Overrides {@link goog.ui.Component#createDom} by + * delegating DOM manipulation to the control's renderer. + * @override + */ +goog.ui.Control.prototype.createDom = function() { + var element = this.renderer_.createDom(this); + this.setElementInternal(element); + + // Initialize ARIA role. + this.renderer_.setAriaRole(element, this.getPreferredAriaRole()); + + // Initialize text selection. + if (!this.isAllowTextSelection()) { + // The renderer is assumed to create selectable elements. Since making + // elements unselectable is expensive, only do it if needed (bug 1037090). + this.renderer_.setAllowTextSelection(element, false); + } + + // Initialize visibility. + if (!this.isVisible()) { + // The renderer is assumed to create visible elements. Since hiding + // elements can be expensive, only do it if needed (bug 1037105). + this.renderer_.setVisible(element, false); + } +}; + + +/** + * Returns the control's preferred ARIA role. This can be used by a control to + * override the role that would be assigned by the renderer. This is useful in + * cases where a different ARIA role is appropriate for a control because of the + * context in which it's used. E.g., a {@link goog.ui.MenuButton} added to a + * {@link goog.ui.Select} should have an ARIA role of LISTBOX and not MENUITEM. + * @return {?goog.a11y.aria.Role} This control's preferred ARIA role or null if + * no preferred ARIA role is set. + */ +goog.ui.Control.prototype.getPreferredAriaRole = function() { + return this.preferredAriaRole_; +}; + + +/** + * Sets the control's preferred ARIA role. This can be used to override the role + * that would be assigned by the renderer. This is useful in cases where a + * different ARIA role is appropriate for a control because of the + * context in which it's used. E.g., a {@link goog.ui.MenuButton} added to a + * {@link goog.ui.Select} should have an ARIA role of LISTBOX and not MENUITEM. + * @param {goog.a11y.aria.Role} role This control's preferred ARIA role. + */ +goog.ui.Control.prototype.setPreferredAriaRole = function(role) { + this.preferredAriaRole_ = role; +}; + + +/** + * Returns the DOM element into which child components are to be rendered, + * or null if the control itself hasn't been rendered yet. Overrides + * {@link goog.ui.Component#getContentElement} by delegating to the renderer. + * @return {Element} Element to contain child elements (null if none). + * @override + */ +goog.ui.Control.prototype.getContentElement = function() { + // Delegate to renderer. + return this.renderer_.getContentElement(this.getElement()); +}; + + +/** + * Returns true if the given element can be decorated by this component. + * Overrides {@link goog.ui.Component#canDecorate}. + * @param {Element} element Element to decorate. + * @return {boolean} Whether the element can be decorated by this component. + * @override + */ +goog.ui.Control.prototype.canDecorate = function(element) { + // Controls support pluggable renderers; delegate to the renderer. + return this.renderer_.canDecorate(element); +}; + + +/** + * Decorates the given element with this component. Overrides {@link + * goog.ui.Component#decorateInternal} by delegating DOM manipulation + * to the control's renderer. + * @param {Element} element Element to decorate. + * @protected + * @override + */ +goog.ui.Control.prototype.decorateInternal = function(element) { + element = this.renderer_.decorate(this, element); + this.setElementInternal(element); + + // Initialize ARIA role. + this.renderer_.setAriaRole(element, this.getPreferredAriaRole()); + + // Initialize text selection. + if (!this.isAllowTextSelection()) { + // Decorated elements are assumed to be selectable. Since making elements + // unselectable is expensive, only do it if needed (bug 1037090). + this.renderer_.setAllowTextSelection(element, false); + } + + // Initialize visibility based on the decorated element's styling. + this.visible_ = element.style.display != 'none'; +}; + + +/** + * Configures the component after its DOM has been rendered, and sets up event + * handling. Overrides {@link goog.ui.Component#enterDocument}. + * @override + */ +goog.ui.Control.prototype.enterDocument = function() { + goog.ui.Control.superClass_.enterDocument.call(this); + + // Call the renderer's initializeDom method to configure properties of the + // control's DOM that can only be done once it's in the document. + this.renderer_.initializeDom(this); + + // Initialize event handling if at least one state other than DISABLED is + // supported. + if (this.supportedStates_ & ~goog.ui.Component.State.DISABLED) { + // Initialize mouse event handling if the control is configured to handle + // its own mouse events. (Controls hosted in containers don't need to + // handle their own mouse events.) + if (this.isHandleMouseEvents()) { + this.enableMouseEventHandling_(true); + } + + // Initialize keyboard event handling if the control is focusable and has + // a key event target. (Controls hosted in containers typically aren't + // focusable, allowing their container to handle keyboard events for them.) + if (this.isSupportedState(goog.ui.Component.State.FOCUSED)) { + var keyTarget = this.getKeyEventTarget(); + if (keyTarget) { + var keyHandler = this.getKeyHandler(); + keyHandler.attach(keyTarget); + this.getHandler(). + listen(keyHandler, goog.events.KeyHandler.EventType.KEY, + this.handleKeyEvent). + listen(keyTarget, goog.events.EventType.FOCUS, + this.handleFocus). + listen(keyTarget, goog.events.EventType.BLUR, + this.handleBlur); + } + } + } +}; + + +/** + * Enables or disables mouse event handling on the control. + * @param {boolean} enable Whether to enable mouse event handling. + * @private + */ +goog.ui.Control.prototype.enableMouseEventHandling_ = function(enable) { + var handler = this.getHandler(); + var element = this.getElement(); + if (enable) { + handler. + listen(element, goog.events.EventType.MOUSEOVER, this.handleMouseOver). + listen(element, goog.events.EventType.MOUSEDOWN, this.handleMouseDown). + listen(element, goog.events.EventType.MOUSEUP, this.handleMouseUp). + listen(element, goog.events.EventType.MOUSEOUT, this.handleMouseOut); + if (this.handleContextMenu != goog.nullFunction) { + handler.listen(element, goog.events.EventType.CONTEXTMENU, + this.handleContextMenu); + } + if (goog.userAgent.IE) { + handler.listen(element, goog.events.EventType.DBLCLICK, + this.handleDblClick); + } + } else { + handler. + unlisten(element, goog.events.EventType.MOUSEOVER, + this.handleMouseOver). + unlisten(element, goog.events.EventType.MOUSEDOWN, + this.handleMouseDown). + unlisten(element, goog.events.EventType.MOUSEUP, this.handleMouseUp). + unlisten(element, goog.events.EventType.MOUSEOUT, this.handleMouseOut); + if (this.handleContextMenu != goog.nullFunction) { + handler.unlisten(element, goog.events.EventType.CONTEXTMENU, + this.handleContextMenu); + } + if (goog.userAgent.IE) { + handler.unlisten(element, goog.events.EventType.DBLCLICK, + this.handleDblClick); + } + } +}; + + +/** + * Cleans up the component before its DOM is removed from the document, and + * removes event handlers. Overrides {@link goog.ui.Component#exitDocument} + * by making sure that components that are removed from the document aren't + * focusable (i.e. have no tab index). + * @override + */ +goog.ui.Control.prototype.exitDocument = function() { + goog.ui.Control.superClass_.exitDocument.call(this); + if (this.keyHandler_) { + this.keyHandler_.detach(); + } + if (this.isVisible() && this.isEnabled()) { + this.renderer_.setFocusable(this, false); + } +}; + + +/** @override */ +goog.ui.Control.prototype.disposeInternal = function() { + goog.ui.Control.superClass_.disposeInternal.call(this); + if (this.keyHandler_) { + this.keyHandler_.dispose(); + delete this.keyHandler_; + } + delete this.renderer_; + this.content_ = null; + this.extraClassNames_ = null; +}; + + +// Component content management. + + +/** + * Returns the text caption or DOM structure displayed in the component. + * @return {goog.ui.ControlContent} Text caption or DOM structure + * comprising the component's contents. + */ +goog.ui.Control.prototype.getContent = function() { + return this.content_; +}; + + +/** + * Sets the component's content to the given text caption, element, or array of + * nodes. (If the argument is an array of nodes, it must be an actual array, + * not an array-like object.) + * @param {goog.ui.ControlContent} content Text caption or DOM + * structure to set as the component's contents. + */ +goog.ui.Control.prototype.setContent = function(content) { + // Controls support pluggable renderers; delegate to the renderer. + this.renderer_.setContent(this.getElement(), content); + + // setContentInternal needs to be after the renderer, since the implementation + // may depend on the content being in the DOM. + this.setContentInternal(content); +}; + + +/** + * Sets the component's content to the given text caption, element, or array + * of nodes. Unlike {@link #setContent}, doesn't modify the component's DOM. + * Called by renderers during element decoration. + * + * This should only be used by subclasses and its associated renderers. + * + * @param {goog.ui.ControlContent} content Text caption or DOM structure + * to set as the component's contents. + */ +goog.ui.Control.prototype.setContentInternal = function(content) { + this.content_ = content; +}; + + +/** + * @return {string} Text caption of the control or empty string if none. + */ +goog.ui.Control.prototype.getCaption = function() { + var content = this.getContent(); + if (!content) { + return ''; + } + var caption = + goog.isString(content) ? content : + goog.isArray(content) ? goog.array.map(content, + goog.dom.getRawTextContent).join('') : + goog.dom.getTextContent(/** @type {!Node} */ (content)); + return goog.string.collapseBreakingSpaces(caption); +}; + + +/** + * Sets the text caption of the component. + * @param {string} caption Text caption of the component. + */ +goog.ui.Control.prototype.setCaption = function(caption) { + this.setContent(caption); +}; + + +// Component state management. + + +/** @override */ +goog.ui.Control.prototype.setRightToLeft = function(rightToLeft) { + // The superclass implementation ensures the control isn't in the document. + goog.ui.Control.superClass_.setRightToLeft.call(this, rightToLeft); + + var element = this.getElement(); + if (element) { + this.renderer_.setRightToLeft(element, rightToLeft); + } +}; + + +/** + * Returns true if the control allows text selection within its DOM, false + * otherwise. Controls that disallow text selection have the appropriate + * unselectable styling applied to their elements. Note that controls hosted + * in containers will report that they allow text selection even if their + * container disallows text selection. + * @return {boolean} Whether the control allows text selection. + */ +goog.ui.Control.prototype.isAllowTextSelection = function() { + return this.allowTextSelection_; +}; + + +/** + * Allows or disallows text selection within the control's DOM. + * @param {boolean} allow Whether the control should allow text selection. + */ +goog.ui.Control.prototype.setAllowTextSelection = function(allow) { + this.allowTextSelection_ = allow; + + var element = this.getElement(); + if (element) { + this.renderer_.setAllowTextSelection(element, allow); + } +}; + + +/** + * Returns true if the component's visibility is set to visible, false if + * it is set to hidden. A component that is set to hidden is guaranteed + * to be hidden from the user, but the reverse isn't necessarily true. + * A component may be set to visible but can otherwise be obscured by another + * element, rendered off-screen, or hidden using direct CSS manipulation. + * @return {boolean} Whether the component is visible. + */ +goog.ui.Control.prototype.isVisible = function() { + return this.visible_; +}; + + +/** + * Shows or hides the component. Does nothing if the component already has + * the requested visibility. Otherwise, dispatches a SHOW or HIDE event as + * appropriate, giving listeners a chance to prevent the visibility change. + * When showing a component that is both enabled and focusable, ensures that + * its key target has a tab index. When hiding a component that is enabled + * and focusable, blurs its key target and removes its tab index. + * @param {boolean} visible Whether to show or hide the component. + * @param {boolean=} opt_force If true, doesn't check whether the component + * already has the requested visibility, and doesn't dispatch any events. + * @return {boolean} Whether the visibility was changed. + */ +goog.ui.Control.prototype.setVisible = function(visible, opt_force) { + if (opt_force || (this.visible_ != visible && this.dispatchEvent(visible ? + goog.ui.Component.EventType.SHOW : goog.ui.Component.EventType.HIDE))) { + var element = this.getElement(); + if (element) { + this.renderer_.setVisible(element, visible); + } + if (this.isEnabled()) { + this.renderer_.setFocusable(this, visible); + } + this.visible_ = visible; + return true; + } + return false; +}; + + +/** + * Returns true if the component is enabled, false otherwise. + * @return {boolean} Whether the component is enabled. + */ +goog.ui.Control.prototype.isEnabled = function() { + return !this.hasState(goog.ui.Component.State.DISABLED); +}; + + +/** + * Returns true if the control has a parent that is itself disabled, false + * otherwise. + * @return {boolean} Whether the component is hosted in a disabled container. + * @private + */ +goog.ui.Control.prototype.isParentDisabled_ = function() { + var parent = this.getParent(); + return !!parent && typeof parent.isEnabled == 'function' && + !parent.isEnabled(); +}; + + +/** + * Enables or disables the component. Does nothing if this state transition + * is disallowed. If the component is both visible and focusable, updates its + * focused state and tab index as needed. If the component is being disabled, + * ensures that it is also deactivated and un-highlighted first. Note that the + * component's enabled/disabled state is "locked" as long as it is hosted in a + * {@link goog.ui.Container} that is itself disabled; this is to prevent clients + * from accidentally re-enabling a control that is in a disabled container. + * @param {boolean} enable Whether to enable or disable the component. + * @see #isTransitionAllowed + */ +goog.ui.Control.prototype.setEnabled = function(enable) { + if (!this.isParentDisabled_() && + this.isTransitionAllowed(goog.ui.Component.State.DISABLED, !enable)) { + if (!enable) { + this.setActive(false); + this.setHighlighted(false); + } + if (this.isVisible()) { + this.renderer_.setFocusable(this, enable); + } + this.setState(goog.ui.Component.State.DISABLED, !enable); + } +}; + + +/** + * Returns true if the component is currently highlighted, false otherwise. + * @return {boolean} Whether the component is highlighted. + */ +goog.ui.Control.prototype.isHighlighted = function() { + return this.hasState(goog.ui.Component.State.HOVER); +}; + + +/** + * Highlights or unhighlights the component. Does nothing if this state + * transition is disallowed. + * @param {boolean} highlight Whether to highlight or unhighlight the component. + * @see #isTransitionAllowed + */ +goog.ui.Control.prototype.setHighlighted = function(highlight) { + if (this.isTransitionAllowed(goog.ui.Component.State.HOVER, highlight)) { + this.setState(goog.ui.Component.State.HOVER, highlight); + } +}; + + +/** + * Returns true if the component is active (pressed), false otherwise. + * @return {boolean} Whether the component is active. + */ +goog.ui.Control.prototype.isActive = function() { + return this.hasState(goog.ui.Component.State.ACTIVE); +}; + + +/** + * Activates or deactivates the component. Does nothing if this state + * transition is disallowed. + * @param {boolean} active Whether to activate or deactivate the component. + * @see #isTransitionAllowed + */ +goog.ui.Control.prototype.setActive = function(active) { + if (this.isTransitionAllowed(goog.ui.Component.State.ACTIVE, active)) { + this.setState(goog.ui.Component.State.ACTIVE, active); + } +}; + + +/** + * Returns true if the component is selected, false otherwise. + * @return {boolean} Whether the component is selected. + */ +goog.ui.Control.prototype.isSelected = function() { + return this.hasState(goog.ui.Component.State.SELECTED); +}; + + +/** + * Selects or unselects the component. Does nothing if this state transition + * is disallowed. + * @param {boolean} select Whether to select or unselect the component. + * @see #isTransitionAllowed + */ +goog.ui.Control.prototype.setSelected = function(select) { + if (this.isTransitionAllowed(goog.ui.Component.State.SELECTED, select)) { + this.setState(goog.ui.Component.State.SELECTED, select); + } +}; + + +/** + * Returns true if the component is checked, false otherwise. + * @return {boolean} Whether the component is checked. + */ +goog.ui.Control.prototype.isChecked = function() { + return this.hasState(goog.ui.Component.State.CHECKED); +}; + + +/** + * Checks or unchecks the component. Does nothing if this state transition + * is disallowed. + * @param {boolean} check Whether to check or uncheck the component. + * @see #isTransitionAllowed + */ +goog.ui.Control.prototype.setChecked = function(check) { + if (this.isTransitionAllowed(goog.ui.Component.State.CHECKED, check)) { + this.setState(goog.ui.Component.State.CHECKED, check); + } +}; + + +/** + * Returns true if the component is styled to indicate that it has keyboard + * focus, false otherwise. Note that {@code isFocused()} returning true + * doesn't guarantee that the component's key event target has keyborad focus, + * only that it is styled as such. + * @return {boolean} Whether the component is styled to indicate as having + * keyboard focus. + */ +goog.ui.Control.prototype.isFocused = function() { + return this.hasState(goog.ui.Component.State.FOCUSED); +}; + + +/** + * Applies or removes styling indicating that the component has keyboard focus. + * Note that unlike the other "set" methods, this method is called as a result + * of the component's element having received or lost keyboard focus, not the + * other way around, so calling {@code setFocused(true)} doesn't guarantee that + * the component's key event target has keyboard focus, only that it is styled + * as such. + * @param {boolean} focused Whether to apply or remove styling to indicate that + * the component's element has keyboard focus. + */ +goog.ui.Control.prototype.setFocused = function(focused) { + if (this.isTransitionAllowed(goog.ui.Component.State.FOCUSED, focused)) { + this.setState(goog.ui.Component.State.FOCUSED, focused); + } +}; + + +/** + * Returns true if the component is open (expanded), false otherwise. + * @return {boolean} Whether the component is open. + */ +goog.ui.Control.prototype.isOpen = function() { + return this.hasState(goog.ui.Component.State.OPENED); +}; + + +/** + * Opens (expands) or closes (collapses) the component. Does nothing if this + * state transition is disallowed. + * @param {boolean} open Whether to open or close the component. + * @see #isTransitionAllowed + */ +goog.ui.Control.prototype.setOpen = function(open) { + if (this.isTransitionAllowed(goog.ui.Component.State.OPENED, open)) { + this.setState(goog.ui.Component.State.OPENED, open); + } +}; + + +/** + * Returns the component's state as a bit mask of {@link + * goog.ui.Component.State}s. + * @return {number} Bit mask representing component state. + */ +goog.ui.Control.prototype.getState = function() { + return this.state_; +}; + + +/** + * Returns true if the component is in the specified state, false otherwise. + * @param {goog.ui.Component.State} state State to check. + * @return {boolean} Whether the component is in the given state. + */ +goog.ui.Control.prototype.hasState = function(state) { + return !!(this.state_ & state); +}; + + +/** + * Sets or clears the given state on the component, and updates its styling + * accordingly. Does nothing if the component is already in the correct state + * or if it doesn't support the specified state. Doesn't dispatch any state + * transition events; use advisedly. + * @param {goog.ui.Component.State} state State to set or clear. + * @param {boolean} enable Whether to set or clear the state (if supported). + */ +goog.ui.Control.prototype.setState = function(state, enable) { + if (this.isSupportedState(state) && enable != this.hasState(state)) { + // Delegate actual styling to the renderer, since it is DOM-specific. + this.renderer_.setState(this, state, enable); + this.state_ = enable ? this.state_ | state : this.state_ & ~state; + } +}; + + +/** + * Sets the component's state to the state represented by a bit mask of + * {@link goog.ui.Component.State}s. Unlike {@link #setState}, doesn't + * update the component's styling, and doesn't reject unsupported states. + * Called by renderers during element decoration. Considered protected; + * should only be used within this package and by subclasses. + * + * This should only be used by subclasses and its associated renderers. + * + * @param {number} state Bit mask representing component state. + */ +goog.ui.Control.prototype.setStateInternal = function(state) { + this.state_ = state; +}; + + +/** + * Returns true if the component supports the specified state, false otherwise. + * @param {goog.ui.Component.State} state State to check. + * @return {boolean} Whether the component supports the given state. + */ +goog.ui.Control.prototype.isSupportedState = function(state) { + return !!(this.supportedStates_ & state); +}; + + +/** + * Enables or disables support for the given state. Disabling support + * for a state while the component is in that state is an error. + * @param {goog.ui.Component.State} state State to support or de-support. + * @param {boolean} support Whether the component should support the state. + * @throws {Error} If disabling support for a state the control is currently in. + */ +goog.ui.Control.prototype.setSupportedState = function(state, support) { + if (this.isInDocument() && this.hasState(state) && !support) { + // Since we hook up event handlers in enterDocument(), this is an error. + throw Error(goog.ui.Component.Error.ALREADY_RENDERED); + } + + if (!support && this.hasState(state)) { + // We are removing support for a state that the component is currently in. + this.setState(state, false); + } + + this.supportedStates_ = support ? + this.supportedStates_ | state : this.supportedStates_ & ~state; +}; + + +/** + * Returns true if the component provides default event handling for the state, + * false otherwise. + * @param {goog.ui.Component.State} state State to check. + * @return {boolean} Whether the component provides default event handling for + * the state. + */ +goog.ui.Control.prototype.isAutoState = function(state) { + return !!(this.autoStates_ & state) && this.isSupportedState(state); +}; + + +/** + * Enables or disables automatic event handling for the given state(s). + * @param {number} states Bit mask of {@link goog.ui.Component.State}s for which + * default event handling is to be enabled or disabled. + * @param {boolean} enable Whether the component should provide default event + * handling for the state(s). + */ +goog.ui.Control.prototype.setAutoStates = function(states, enable) { + this.autoStates_ = enable ? + this.autoStates_ | states : this.autoStates_ & ~states; +}; + + +/** + * Returns true if the component is set to dispatch transition events for the + * given state, false otherwise. + * @param {goog.ui.Component.State} state State to check. + * @return {boolean} Whether the component dispatches transition events for + * the state. + */ +goog.ui.Control.prototype.isDispatchTransitionEvents = function(state) { + return !!(this.statesWithTransitionEvents_ & state) && + this.isSupportedState(state); +}; + + +/** + * Enables or disables transition events for the given state(s). Controls + * handle state transitions internally by default, and only dispatch state + * transition events if explicitly requested to do so by calling this method. + * @param {number} states Bit mask of {@link goog.ui.Component.State}s for + * which transition events should be enabled or disabled. + * @param {boolean} enable Whether transition events should be enabled. + */ +goog.ui.Control.prototype.setDispatchTransitionEvents = function(states, + enable) { + this.statesWithTransitionEvents_ = enable ? + this.statesWithTransitionEvents_ | states : + this.statesWithTransitionEvents_ & ~states; +}; + + +/** + * Returns true if the transition into or out of the given state is allowed to + * proceed, false otherwise. A state transition is allowed under the following + * conditions: + * <ul> + * <li>the component supports the state, + * <li>the component isn't already in the target state, + * <li>either the component is configured not to dispatch events for this + * state transition, or a transition event was dispatched and wasn't + * canceled by any event listener, and + * <li>the component hasn't been disposed of + * </ul> + * Considered protected; should only be used within this package and by + * subclasses. + * @param {goog.ui.Component.State} state State to/from which the control is + * transitioning. + * @param {boolean} enable Whether the control is entering or leaving the state. + * @return {boolean} Whether the state transition is allowed to proceed. + * @protected + */ +goog.ui.Control.prototype.isTransitionAllowed = function(state, enable) { + return this.isSupportedState(state) && + this.hasState(state) != enable && + (!(this.statesWithTransitionEvents_ & state) || this.dispatchEvent( + goog.ui.Component.getStateTransitionEvent(state, enable))) && + !this.isDisposed(); +}; + + +// Default event handlers, to be overridden in subclasses. + + +/** + * Handles mouseover events. Dispatches an ENTER event; if the event isn't + * canceled, the component is enabled, and it supports auto-highlighting, + * highlights the component. Considered protected; should only be used + * within this package and by subclasses. + * @param {goog.events.BrowserEvent} e Mouse event to handle. + */ +goog.ui.Control.prototype.handleMouseOver = function(e) { + // Ignore mouse moves between descendants. + if (!goog.ui.Control.isMouseEventWithinElement_(e, this.getElement()) && + this.dispatchEvent(goog.ui.Component.EventType.ENTER) && + this.isEnabled() && + this.isAutoState(goog.ui.Component.State.HOVER)) { + this.setHighlighted(true); + } +}; + + +/** + * Handles mouseout events. Dispatches a LEAVE event; if the event isn't + * canceled, and the component supports auto-highlighting, deactivates and + * un-highlights the component. Considered protected; should only be used + * within this package and by subclasses. + * @param {goog.events.BrowserEvent} e Mouse event to handle. + */ +goog.ui.Control.prototype.handleMouseOut = function(e) { + if (!goog.ui.Control.isMouseEventWithinElement_(e, this.getElement()) && + this.dispatchEvent(goog.ui.Component.EventType.LEAVE)) { + if (this.isAutoState(goog.ui.Component.State.ACTIVE)) { + // Deactivate on mouseout; otherwise we lose track of the mouse button. + this.setActive(false); + } + if (this.isAutoState(goog.ui.Component.State.HOVER)) { + this.setHighlighted(false); + } + } +}; + + +/** + * Handles contextmenu events. + * @param {goog.events.BrowserEvent} e Event to handle. + */ +goog.ui.Control.prototype.handleContextMenu = goog.nullFunction; + + +/** + * Checks if a mouse event (mouseover or mouseout) occured below an element. + * @param {goog.events.BrowserEvent} e Mouse event (should be mouseover or + * mouseout). + * @param {Element} elem The ancestor element. + * @return {boolean} Whether the event has a relatedTarget (the element the + * mouse is coming from) and it's a descendent of elem. + * @private + */ +goog.ui.Control.isMouseEventWithinElement_ = function(e, elem) { + // If relatedTarget is null, it means there was no previous element (e.g. + // the mouse moved out of the window). Assume this means that the mouse + // event was not within the element. + return !!e.relatedTarget && goog.dom.contains(elem, e.relatedTarget); +}; + + +/** + * Handles mousedown events. If the component is enabled, highlights and + * activates it. If the component isn't configured for keyboard access, + * prevents it from receiving keyboard focus. Considered protected; should + * only be used within this package and by subclasses. + * @param {goog.events.Event} e Mouse event to handle. + */ +goog.ui.Control.prototype.handleMouseDown = function(e) { + if (this.isEnabled()) { + // Highlight enabled control on mousedown, regardless of the mouse button. + if (this.isAutoState(goog.ui.Component.State.HOVER)) { + this.setHighlighted(true); + } + + // For the left button only, activate the control, and focus its key event + // target (if supported). + if (e.isMouseActionButton()) { + if (this.isAutoState(goog.ui.Component.State.ACTIVE)) { + this.setActive(true); + } + if (this.renderer_.isFocusable(this)) { + this.getKeyEventTarget().focus(); + } + } + } + + // Cancel the default action unless the control allows text selection. + if (!this.isAllowTextSelection() && e.isMouseActionButton()) { + e.preventDefault(); + } +}; + + +/** + * Handles mouseup events. If the component is enabled, highlights it. If + * the component has previously been activated, performs its associated action + * by calling {@link performActionInternal}, then deactivates it. Considered + * protected; should only be used within this package and by subclasses. + * @param {goog.events.Event} e Mouse event to handle. + */ +goog.ui.Control.prototype.handleMouseUp = function(e) { + if (this.isEnabled()) { + if (this.isAutoState(goog.ui.Component.State.HOVER)) { + this.setHighlighted(true); + } + if (this.isActive() && + this.performActionInternal(e) && + this.isAutoState(goog.ui.Component.State.ACTIVE)) { + this.setActive(false); + } + } +}; + + +/** + * Handles dblclick events. Should only be registered if the user agent is + * IE. If the component is enabled, performs its associated action by calling + * {@link performActionInternal}. This is used to allow more performant + * buttons in IE. In IE, no mousedown event is fired when that mousedown will + * trigger a dblclick event. Because of this, a user clicking quickly will + * only cause ACTION events to fire on every other click. This is a workaround + * to generate ACTION events for every click. Unfortunately, this workaround + * won't ever trigger the ACTIVE state. This is roughly the same behaviour as + * if this were a 'button' element with a listener on mouseup. Considered + * protected; should only be used within this package and by subclasses. + * @param {goog.events.Event} e Mouse event to handle. + */ +goog.ui.Control.prototype.handleDblClick = function(e) { + if (this.isEnabled()) { + this.performActionInternal(e); + } +}; + + +/** + * Performs the appropriate action when the control is activated by the user. + * The default implementation first updates the checked and selected state of + * controls that support them, then dispatches an ACTION event. Considered + * protected; should only be used within this package and by subclasses. + * @param {goog.events.Event} e Event that triggered the action. + * @return {boolean} Whether the action is allowed to proceed. + * @protected + */ +goog.ui.Control.prototype.performActionInternal = function(e) { + if (this.isAutoState(goog.ui.Component.State.CHECKED)) { + this.setChecked(!this.isChecked()); + } + if (this.isAutoState(goog.ui.Component.State.SELECTED)) { + this.setSelected(true); + } + if (this.isAutoState(goog.ui.Component.State.OPENED)) { + this.setOpen(!this.isOpen()); + } + + var actionEvent = new goog.events.Event(goog.ui.Component.EventType.ACTION, + this); + if (e) { + actionEvent.altKey = e.altKey; + actionEvent.ctrlKey = e.ctrlKey; + actionEvent.metaKey = e.metaKey; + actionEvent.shiftKey = e.shiftKey; + actionEvent.platformModifierKey = e.platformModifierKey; + } + return this.dispatchEvent(actionEvent); +}; + + +/** + * Handles focus events on the component's key event target element. If the + * component is focusable, updates its state and styling to indicate that it + * now has keyboard focus. Considered protected; should only be used within + * this package and by subclasses. <b>Warning:</b> IE dispatches focus and + * blur events asynchronously! + * @param {goog.events.Event} e Focus event to handle. + */ +goog.ui.Control.prototype.handleFocus = function(e) { + if (this.isAutoState(goog.ui.Component.State.FOCUSED)) { + this.setFocused(true); + } +}; + + +/** + * Handles blur events on the component's key event target element. Always + * deactivates the component. In addition, if the component is focusable, + * updates its state and styling to indicate that it no longer has keyboard + * focus. Considered protected; should only be used within this package and + * by subclasses. <b>Warning:</b> IE dispatches focus and blur events + * asynchronously! + * @param {goog.events.Event} e Blur event to handle. + */ +goog.ui.Control.prototype.handleBlur = function(e) { + if (this.isAutoState(goog.ui.Component.State.ACTIVE)) { + this.setActive(false); + } + if (this.isAutoState(goog.ui.Component.State.FOCUSED)) { + this.setFocused(false); + } +}; + + +/** + * Attempts to handle a keyboard event, if the component is enabled and visible, + * by calling {@link handleKeyEventInternal}. Considered protected; should only + * be used within this package and by subclasses. + * @param {goog.events.KeyEvent} e Key event to handle. + * @return {boolean} Whether the key event was handled. + */ +goog.ui.Control.prototype.handleKeyEvent = function(e) { + if (this.isVisible() && this.isEnabled() && + this.handleKeyEventInternal(e)) { + e.preventDefault(); + e.stopPropagation(); + return true; + } + return false; +}; + + +/** + * Attempts to handle a keyboard event; returns true if the event was handled, + * false otherwise. Considered protected; should only be used within this + * package and by subclasses. + * @param {goog.events.KeyEvent} e Key event to handle. + * @return {boolean} Whether the key event was handled. + * @protected + */ +goog.ui.Control.prototype.handleKeyEventInternal = function(e) { + return e.keyCode == goog.events.KeyCodes.ENTER && + this.performActionInternal(e); +}; + + +// Register the default renderer for goog.ui.Controls. +goog.ui.registry.setDefaultRenderer(goog.ui.Control, goog.ui.ControlRenderer); + + +// Register a decorator factory function for goog.ui.Controls. +goog.ui.registry.setDecoratorByClassName(goog.ui.ControlRenderer.CSS_CLASS, + function() { + return new goog.ui.Control(null); + }); diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/controlcontent.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/controlcontent.js new file mode 100644 index 0000000..c6f6571 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/controlcontent.js @@ -0,0 +1,28 @@ +// Copyright 2009 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Type declaration for control content. + * + * @author nicksantos@google.com (Nick Santos) + */ +goog.provide('goog.ui.ControlContent'); + + +/** + * Type declaration for text caption or DOM structure to be used as the content + * of {@link goog.ui.Control}s. + * @typedef {string|Node|Array.<Node>|NodeList} + */ +goog.ui.ControlContent; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/controlrenderer.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/controlrenderer.js new file mode 100644 index 0000000..e16d1a2 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/controlrenderer.js @@ -0,0 +1,927 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Base class for control renderers. + * TODO(attila): If the renderer framework works well, pull it into Component. + * + * @author attila@google.com (Attila Bodis) + */ + +goog.provide('goog.ui.ControlRenderer'); + +goog.require('goog.a11y.aria'); +goog.require('goog.a11y.aria.Role'); +goog.require('goog.a11y.aria.State'); +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.classlist'); +goog.require('goog.object'); +goog.require('goog.string'); +goog.require('goog.style'); +goog.require('goog.ui.Component'); +goog.require('goog.userAgent'); + + + +/** + * Default renderer for {@link goog.ui.Control}s. Can be used as-is, but + * subclasses of Control will probably want to use renderers specifically + * tailored for them by extending this class. Controls that use renderers + * delegate one or more of the following API methods to the renderer: + * <ul> + * <li>{@code createDom} - renders the DOM for the component + * <li>{@code canDecorate} - determines whether an element can be decorated + * by the component + * <li>{@code decorate} - decorates an existing element with the component + * <li>{@code setState} - updates the appearance of the component based on + * its state + * <li>{@code getContent} - returns the component's content + * <li>{@code setContent} - sets the component's content + * </ul> + * Controls are stateful; renderers, on the other hand, should be stateless and + * reusable. + * @constructor + */ +goog.ui.ControlRenderer = function() { +}; +goog.addSingletonGetter(goog.ui.ControlRenderer); +goog.tagUnsealableClass(goog.ui.ControlRenderer); + + +/** + * Constructs a new renderer and sets the CSS class that the renderer will use + * as the base CSS class to apply to all elements rendered by that renderer. + * An example to use this function using a color palette: + * + * <pre> + * var myCustomRenderer = goog.ui.ControlRenderer.getCustomRenderer( + * goog.ui.PaletteRenderer, 'my-special-palette'); + * var newColorPalette = new goog.ui.ColorPalette( + * colors, myCustomRenderer, opt_domHelper); + * </pre> + * + * Your CSS can look like this now: + * <pre> + * .my-special-palette { } + * .my-special-palette-table { } + * .my-special-palette-cell { } + * etc. + * </pre> + * + * <em>instead</em> of + * <pre> + * .CSS_MY_SPECIAL_PALETTE .goog-palette { } + * .CSS_MY_SPECIAL_PALETTE .goog-palette-table { } + * .CSS_MY_SPECIAL_PALETTE .goog-palette-cell { } + * etc. + * </pre> + * + * You would want to use this functionality when you want an instance of a + * component to have specific styles different than the other components of the + * same type in your application. This avoids using descendant selectors to + * apply the specific styles to this component. + * + * @param {Function} ctor The constructor of the renderer you are trying to + * create. + * @param {string} cssClassName The name of the CSS class for this renderer. + * @return {goog.ui.ControlRenderer} An instance of the desired renderer with + * its getCssClass() method overridden to return the supplied custom CSS + * class name. + */ +goog.ui.ControlRenderer.getCustomRenderer = function(ctor, cssClassName) { + var renderer = new ctor(); + + /** + * Returns the CSS class to be applied to the root element of components + * rendered using this renderer. + * @return {string} Renderer-specific CSS class. + */ + renderer.getCssClass = function() { + return cssClassName; + }; + + return renderer; +}; + + +/** + * Default CSS class to be applied to the root element of components rendered + * by this renderer. + * @type {string} + */ +goog.ui.ControlRenderer.CSS_CLASS = goog.getCssName('goog-control'); + + +/** + * Array of arrays of CSS classes that we want composite classes added and + * removed for in IE6 and lower as a workaround for lack of multi-class CSS + * selector support. + * + * Subclasses that have accompanying CSS requiring this workaround should define + * their own static IE6_CLASS_COMBINATIONS constant and override + * getIe6ClassCombinations to return it. + * + * For example, if your stylesheet uses the selector .button.collapse-left + * (and is compiled to .button_collapse-left for the IE6 version of the + * stylesheet,) you should include ['button', 'collapse-left'] in this array + * and the class button_collapse-left will be applied to the root element + * whenever both button and collapse-left are applied individually. + * + * Members of each class name combination will be joined with underscores in the + * order that they're defined in the array. You should alphabetize them (for + * compatibility with the CSS compiler) unless you are doing something special. + * @type {Array.<Array.<string>>} + */ +goog.ui.ControlRenderer.IE6_CLASS_COMBINATIONS = []; + + +/** + * Map of component states to corresponding ARIA attributes. Since the mapping + * of component states to ARIA attributes is neither component- nor + * renderer-specific, this is a static property of the renderer class, and is + * initialized on first use. + * @type {Object.<goog.ui.Component.State, goog.a11y.aria.State>} + * @private + * @const + */ +goog.ui.ControlRenderer.ARIA_ATTRIBUTE_MAP_; + + +/** + * Map of certain ARIA states to ARIA roles that support them. Used for checked + * and selected Component states because they are used on Components with ARIA + * roles that do not support the corresponding ARIA state. + * @private {!Object.<goog.a11y.aria.Role, goog.a11y.aria.State>} + * @const + */ +goog.ui.ControlRenderer.TOGGLE_ARIA_STATE_MAP_ = goog.object.create( + goog.a11y.aria.Role.BUTTON, goog.a11y.aria.State.PRESSED, + goog.a11y.aria.Role.CHECKBOX, goog.a11y.aria.State.CHECKED, + goog.a11y.aria.Role.MENU_ITEM, goog.a11y.aria.State.SELECTED, + goog.a11y.aria.Role.MENU_ITEM_CHECKBOX, goog.a11y.aria.State.CHECKED, + goog.a11y.aria.Role.MENU_ITEM_RADIO, goog.a11y.aria.State.CHECKED, + goog.a11y.aria.Role.RADIO, goog.a11y.aria.State.CHECKED, + goog.a11y.aria.Role.TAB, goog.a11y.aria.State.SELECTED, + goog.a11y.aria.Role.TREEITEM, goog.a11y.aria.State.SELECTED); + + +/** + * Returns the ARIA role to be applied to the control. + * See http://wiki/Main/ARIA for more info. + * @return {goog.a11y.aria.Role|undefined} ARIA role. + */ +goog.ui.ControlRenderer.prototype.getAriaRole = function() { + // By default, the ARIA role is unspecified. + return undefined; +}; + + +/** + * Returns the control's contents wrapped in a DIV, with the renderer's own + * CSS class and additional state-specific classes applied to it. + * @param {goog.ui.Control} control Control to render. + * @return {Element} Root element for the control. + */ +goog.ui.ControlRenderer.prototype.createDom = function(control) { + // Create and return DIV wrapping contents. + var element = control.getDomHelper().createDom( + 'div', this.getClassNames(control).join(' '), control.getContent()); + + this.setAriaStates(control, element); + return element; +}; + + +/** + * Takes the control's root element and returns the parent element of the + * control's contents. Since by default controls are rendered as a single + * DIV, the default implementation returns the element itself. Subclasses + * with more complex DOM structures must override this method as needed. + * @param {Element} element Root element of the control whose content element + * is to be returned. + * @return {Element} The control's content element. + */ +goog.ui.ControlRenderer.prototype.getContentElement = function(element) { + return element; +}; + + +/** + * Updates the control's DOM by adding or removing the specified class name + * to/from its root element. May add additional combined classes as needed in + * IE6 and lower. Because of this, subclasses should use this method when + * modifying class names on the control's root element. + * @param {goog.ui.Control|Element} control Control instance (or root element) + * to be updated. + * @param {string} className CSS class name to add or remove. + * @param {boolean} enable Whether to add or remove the class name. + */ +goog.ui.ControlRenderer.prototype.enableClassName = function(control, + className, enable) { + var element = /** @type {Element} */ ( + control.getElement ? control.getElement() : control); + if (element) { + var classNames = [className]; + + // For IE6, we need to enable any combined classes involving this class + // as well. + // TODO(user): Remove this as IE6 is no longer in use. + if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('7')) { + classNames = this.getAppliedCombinedClassNames_( + goog.dom.classlist.get(element), className); + classNames.push(className); + } + + goog.dom.classlist.enableAll(element, classNames, enable); + } +}; + + +/** + * Updates the control's DOM by adding or removing the specified extra class + * name to/from its element. + * @param {goog.ui.Control} control Control to be updated. + * @param {string} className CSS class name to add or remove. + * @param {boolean} enable Whether to add or remove the class name. + */ +goog.ui.ControlRenderer.prototype.enableExtraClassName = function(control, + className, enable) { + // The base class implementation is trivial; subclasses should override as + // needed. + this.enableClassName(control, className, enable); +}; + + +/** + * Returns true if this renderer can decorate the element, false otherwise. + * The default implementation always returns true. + * @param {Element} element Element to decorate. + * @return {boolean} Whether the renderer can decorate the element. + */ +goog.ui.ControlRenderer.prototype.canDecorate = function(element) { + return true; +}; + + +/** + * Default implementation of {@code decorate} for {@link goog.ui.Control}s. + * Initializes the control's ID, content, and state based on the ID of the + * element, its child nodes, and its CSS classes, respectively. Returns the + * element. + * @param {goog.ui.Control} control Control instance to decorate the element. + * @param {Element} element Element to decorate. + * @return {Element} Decorated element. + */ +goog.ui.ControlRenderer.prototype.decorate = function(control, element) { + // Set the control's ID to the decorated element's DOM ID, if any. + if (element.id) { + control.setId(element.id); + } + + // Set the control's content to the decorated element's content. + var contentElem = this.getContentElement(element); + if (contentElem && contentElem.firstChild) { + control.setContentInternal(contentElem.firstChild.nextSibling ? + goog.array.clone(contentElem.childNodes) : contentElem.firstChild); + } else { + control.setContentInternal(null); + } + + // Initialize the control's state based on the decorated element's CSS class. + // This implementation is optimized to minimize object allocations, string + // comparisons, and DOM access. + var state = 0x00; + var rendererClassName = this.getCssClass(); + var structuralClassName = this.getStructuralCssClass(); + var hasRendererClassName = false; + var hasStructuralClassName = false; + var hasCombinedClassName = false; + var classNames = goog.array.toArray(goog.dom.classlist.get(element)); + goog.array.forEach(classNames, function(className) { + if (!hasRendererClassName && className == rendererClassName) { + hasRendererClassName = true; + if (structuralClassName == rendererClassName) { + hasStructuralClassName = true; + } + } else if (!hasStructuralClassName && className == structuralClassName) { + hasStructuralClassName = true; + } else { + state |= this.getStateFromClass(className); + } + }, this); + control.setStateInternal(state); + + // Make sure the element has the renderer's CSS classes applied, as well as + // any extra class names set on the control. + if (!hasRendererClassName) { + classNames.push(rendererClassName); + if (structuralClassName == rendererClassName) { + hasStructuralClassName = true; + } + } + if (!hasStructuralClassName) { + classNames.push(structuralClassName); + } + var extraClassNames = control.getExtraClassNames(); + if (extraClassNames) { + classNames.push.apply(classNames, extraClassNames); + } + + // For IE6, rewrite all classes on the decorated element if any combined + // classes apply. + if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('7')) { + var combinedClasses = this.getAppliedCombinedClassNames_( + classNames); + if (combinedClasses.length > 0) { + classNames.push.apply(classNames, combinedClasses); + hasCombinedClassName = true; + } + } + + // Only write to the DOM if new class names had to be added to the element. + if (!hasRendererClassName || !hasStructuralClassName || + extraClassNames || hasCombinedClassName) { + goog.dom.classlist.set(element, classNames.join(' ')); + } + + this.setAriaStates(control, element); + return element; +}; + + +/** + * Initializes the control's DOM by configuring properties that can only be set + * after the DOM has entered the document. This implementation sets up BiDi + * and keyboard focus. Called from {@link goog.ui.Control#enterDocument}. + * @param {goog.ui.Control} control Control whose DOM is to be initialized + * as it enters the document. + */ +goog.ui.ControlRenderer.prototype.initializeDom = function(control) { + // Initialize render direction (BiDi). We optimize the left-to-right render + // direction by assuming that elements are left-to-right by default, and only + // updating their styling if they are explicitly set to right-to-left. + if (control.isRightToLeft()) { + this.setRightToLeft(control.getElement(), true); + } + + // Initialize keyboard focusability (tab index). We assume that components + // aren't focusable by default (i.e have no tab index), and only touch the + // DOM if the component is focusable, enabled, and visible, and therefore + // needs a tab index. + if (control.isEnabled()) { + this.setFocusable(control, control.isVisible()); + } +}; + + +/** + * Sets the element's ARIA role. + * @param {Element} element Element to update. + * @param {?goog.a11y.aria.Role=} opt_preferredRole The preferred ARIA role. + */ +goog.ui.ControlRenderer.prototype.setAriaRole = function(element, + opt_preferredRole) { + var ariaRole = opt_preferredRole || this.getAriaRole(); + if (ariaRole) { + goog.asserts.assert(element, + 'The element passed as a first parameter cannot be null.'); + var currentRole = goog.a11y.aria.getRole(element); + if (ariaRole == currentRole) { + return; + } + goog.a11y.aria.setRole(element, ariaRole); + } +}; + + +/** + * Sets the element's ARIA attributes, including distinguishing between + * universally supported ARIA properties and ARIA states that are only + * supported by certain ARIA roles. Only attributes which are initialized to be + * true will be set. + * @param {!goog.ui.Control} control Control whose ARIA state will be updated. + * @param {!Element} element Element whose ARIA state is to be updated. + */ +goog.ui.ControlRenderer.prototype.setAriaStates = function(control, element) { + goog.asserts.assert(control); + goog.asserts.assert(element); + + if (!control.isVisible()) { + goog.a11y.aria.setState( + element, goog.a11y.aria.State.HIDDEN, !control.isVisible()); + } + if (!control.isEnabled()) { + this.updateAriaState( + element, goog.ui.Component.State.DISABLED, !control.isEnabled()); + } + if (control.isSupportedState(goog.ui.Component.State.SELECTED)) { + this.updateAriaState( + element, goog.ui.Component.State.SELECTED, control.isSelected()); + } + if (control.isSupportedState(goog.ui.Component.State.CHECKED)) { + this.updateAriaState( + element, goog.ui.Component.State.CHECKED, control.isChecked()); + } + if (control.isSupportedState(goog.ui.Component.State.OPENED)) { + this.updateAriaState( + element, goog.ui.Component.State.OPENED, control.isOpen()); + } +}; + + +/** + * Allows or disallows text selection within the control's DOM. + * @param {Element} element The control's root element. + * @param {boolean} allow Whether the element should allow text selection. + */ +goog.ui.ControlRenderer.prototype.setAllowTextSelection = function(element, + allow) { + // On all browsers other than IE and Opera, it isn't necessary to recursively + // apply unselectable styling to the element's children. + goog.style.setUnselectable(element, !allow, + !goog.userAgent.IE && !goog.userAgent.OPERA); +}; + + +/** + * Applies special styling to/from the control's element if it is rendered + * right-to-left, and removes it if it is rendered left-to-right. + * @param {Element} element The control's root element. + * @param {boolean} rightToLeft Whether the component is rendered + * right-to-left. + */ +goog.ui.ControlRenderer.prototype.setRightToLeft = function(element, + rightToLeft) { + this.enableClassName(element, + goog.getCssName(this.getStructuralCssClass(), 'rtl'), rightToLeft); +}; + + +/** + * Returns true if the control's key event target supports keyboard focus + * (based on its {@code tabIndex} attribute), false otherwise. + * @param {goog.ui.Control} control Control whose key event target is to be + * checked. + * @return {boolean} Whether the control's key event target is focusable. + */ +goog.ui.ControlRenderer.prototype.isFocusable = function(control) { + var keyTarget; + if (control.isSupportedState(goog.ui.Component.State.FOCUSED) && + (keyTarget = control.getKeyEventTarget())) { + return goog.dom.isFocusableTabIndex(keyTarget); + } + return false; +}; + + +/** + * Updates the control's key event target to make it focusable or non-focusable + * via its {@code tabIndex} attribute. Does nothing if the control doesn't + * support the {@code FOCUSED} state, or if it has no key event target. + * @param {goog.ui.Control} control Control whose key event target is to be + * updated. + * @param {boolean} focusable Whether to enable keyboard focus support on the + * control's key event target. + */ +goog.ui.ControlRenderer.prototype.setFocusable = function(control, focusable) { + var keyTarget; + if (control.isSupportedState(goog.ui.Component.State.FOCUSED) && + (keyTarget = control.getKeyEventTarget())) { + if (!focusable && control.isFocused()) { + // Blur before hiding. Note that IE calls onblur handlers asynchronously. + try { + keyTarget.blur(); + } catch (e) { + // TODO(user|user): Find out why this fails on IE. + } + // The blur event dispatched by the key event target element when blur() + // was called on it should have been handled by the control's handleBlur() + // method, so at this point the control should no longer be focused. + // However, blur events are unreliable on IE and FF3, so if at this point + // the control is still focused, we trigger its handleBlur() method + // programmatically. + if (control.isFocused()) { + control.handleBlur(null); + } + } + // Don't overwrite existing tab index values unless needed. + if (goog.dom.isFocusableTabIndex(keyTarget) != focusable) { + goog.dom.setFocusableTabIndex(keyTarget, focusable); + } + } +}; + + +/** + * Shows or hides the element. + * @param {Element} element Element to update. + * @param {boolean} visible Whether to show the element. + */ +goog.ui.ControlRenderer.prototype.setVisible = function(element, visible) { + // The base class implementation is trivial; subclasses should override as + // needed. It should be possible to do animated reveals, for example. + goog.style.setElementShown(element, visible); + if (element) { + goog.a11y.aria.setState(element, goog.a11y.aria.State.HIDDEN, !visible); + } +}; + + +/** + * Updates the appearance of the control in response to a state change. + * @param {goog.ui.Control} control Control instance to update. + * @param {goog.ui.Component.State} state State to enable or disable. + * @param {boolean} enable Whether the control is entering or exiting the state. + */ +goog.ui.ControlRenderer.prototype.setState = function(control, state, enable) { + var element = control.getElement(); + if (element) { + var className = this.getClassForState(state); + if (className) { + this.enableClassName(control, className, enable); + } + this.updateAriaState(element, state, enable); + } +}; + + +/** + * Updates the element's ARIA (accessibility) attributes , including + * distinguishing between universally supported ARIA properties and ARIA states + * that are only supported by certain ARIA roles. + * @param {Element} element Element whose ARIA state is to be updated. + * @param {goog.ui.Component.State} state Component state being enabled or + * disabled. + * @param {boolean} enable Whether the state is being enabled or disabled. + * @protected + */ +goog.ui.ControlRenderer.prototype.updateAriaState = function(element, state, + enable) { + // Ensure the ARIA attribute map exists. + if (!goog.ui.ControlRenderer.ARIA_ATTRIBUTE_MAP_) { + goog.ui.ControlRenderer.ARIA_ATTRIBUTE_MAP_ = goog.object.create( + goog.ui.Component.State.DISABLED, goog.a11y.aria.State.DISABLED, + goog.ui.Component.State.SELECTED, goog.a11y.aria.State.SELECTED, + goog.ui.Component.State.CHECKED, goog.a11y.aria.State.CHECKED, + goog.ui.Component.State.OPENED, goog.a11y.aria.State.EXPANDED); + } + goog.asserts.assert(element, + 'The element passed as a first parameter cannot be null.'); + var ariaAttr = goog.ui.ControlRenderer.getAriaStateForAriaRole_( + element, goog.ui.ControlRenderer.ARIA_ATTRIBUTE_MAP_[state]); + if (ariaAttr) { + goog.a11y.aria.setState(element, ariaAttr, enable); + } +}; + + +/** + * Returns the appropriate ARIA attribute based on ARIA role if the ARIA + * attribute is an ARIA state. + * @param {!Element} element The element from which to get the ARIA role for + * matching ARIA state. + * @param {goog.a11y.aria.State} attr The ARIA attribute to check to see if it + * can be applied to the given ARIA role. + * @return {goog.a11y.aria.State} An ARIA attribute that can be applied to the + * given ARIA role. + * @private + */ +goog.ui.ControlRenderer.getAriaStateForAriaRole_ = function(element, attr) { + var role = goog.a11y.aria.getRole(element); + if (!role) { + return attr; + } + role = /** @type {goog.a11y.aria.Role} */ (role); + var matchAttr = goog.ui.ControlRenderer.TOGGLE_ARIA_STATE_MAP_[role] || attr; + return goog.ui.ControlRenderer.isAriaState_(attr) ? matchAttr : attr; +}; + + +/** + * Determines if the given ARIA attribute is an ARIA property or ARIA state. + * @param {goog.a11y.aria.State} attr The ARIA attribute to classify. + * @return {boolean} If the ARIA attribute is an ARIA state. + * @private + */ +goog.ui.ControlRenderer.isAriaState_ = function(attr) { + return attr == goog.a11y.aria.State.CHECKED || + attr == goog.a11y.aria.State.SELECTED; +}; + + +/** + * Takes a control's root element, and sets its content to the given text + * caption or DOM structure. The default implementation replaces the children + * of the given element. Renderers that create more complex DOM structures + * must override this method accordingly. + * @param {Element} element The control's root element. + * @param {goog.ui.ControlContent} content Text caption or DOM structure to be + * set as the control's content. The DOM nodes will not be cloned, they + * will only moved under the content element of the control. + */ +goog.ui.ControlRenderer.prototype.setContent = function(element, content) { + var contentElem = this.getContentElement(element); + if (contentElem) { + goog.dom.removeChildren(contentElem); + if (content) { + if (goog.isString(content)) { + goog.dom.setTextContent(contentElem, content); + } else { + var childHandler = function(child) { + if (child) { + var doc = goog.dom.getOwnerDocument(contentElem); + contentElem.appendChild(goog.isString(child) ? + doc.createTextNode(child) : child); + } + }; + if (goog.isArray(content)) { + // Array of nodes. + goog.array.forEach(content, childHandler); + } else if (goog.isArrayLike(content) && !('nodeType' in content)) { + // NodeList. The second condition filters out TextNode which also has + // length attribute but is not array like. The nodes have to be cloned + // because childHandler removes them from the list during iteration. + goog.array.forEach(goog.array.clone(/** @type {NodeList} */(content)), + childHandler); + } else { + // Node or string. + childHandler(content); + } + } + } + } +}; + + +/** + * Returns the element within the component's DOM that should receive keyboard + * focus (null if none). The default implementation returns the control's root + * element. + * @param {goog.ui.Control} control Control whose key event target is to be + * returned. + * @return {Element} The key event target. + */ +goog.ui.ControlRenderer.prototype.getKeyEventTarget = function(control) { + return control.getElement(); +}; + + +// CSS class name management. + + +/** + * Returns the CSS class name to be applied to the root element of all + * components rendered or decorated using this renderer. The class name + * is expected to uniquely identify the renderer class, i.e. no two + * renderer classes are expected to share the same CSS class name. + * @return {string} Renderer-specific CSS class name. + */ +goog.ui.ControlRenderer.prototype.getCssClass = function() { + return goog.ui.ControlRenderer.CSS_CLASS; +}; + + +/** + * Returns an array of combinations of classes to apply combined class names for + * in IE6 and below. See {@link IE6_CLASS_COMBINATIONS} for more detail. This + * method doesn't reference {@link IE6_CLASS_COMBINATIONS} so that it can be + * compiled out, but subclasses should return their IE6_CLASS_COMBINATIONS + * static constant instead. + * @return {Array.<Array.<string>>} Array of class name combinations. + */ +goog.ui.ControlRenderer.prototype.getIe6ClassCombinations = function() { + return []; +}; + + +/** + * Returns the name of a DOM structure-specific CSS class to be applied to the + * root element of all components rendered or decorated using this renderer. + * Unlike the class name returned by {@link #getCssClass}, the structural class + * name may be shared among different renderers that generate similar DOM + * structures. The structural class name also serves as the basis of derived + * class names used to identify and style structural elements of the control's + * DOM, as well as the basis for state-specific class names. The default + * implementation returns the same class name as {@link #getCssClass}, but + * subclasses are expected to override this method as needed. + * @return {string} DOM structure-specific CSS class name (same as the renderer- + * specific CSS class name by default). + */ +goog.ui.ControlRenderer.prototype.getStructuralCssClass = function() { + return this.getCssClass(); +}; + + +/** + * Returns all CSS class names applicable to the given control, based on its + * state. The return value is an array of strings containing + * <ol> + * <li>the renderer-specific CSS class returned by {@link #getCssClass}, + * followed by + * <li>the structural CSS class returned by {@link getStructuralCssClass} (if + * different from the renderer-specific CSS class), followed by + * <li>any state-specific classes returned by {@link #getClassNamesForState}, + * followed by + * <li>any extra classes returned by the control's {@code getExtraClassNames} + * method and + * <li>for IE6 and lower, additional combined classes from + * {@link getAppliedCombinedClassNames_}. + * </ol> + * Since all controls have at least one renderer-specific CSS class name, this + * method is guaranteed to return an array of at least one element. + * @param {goog.ui.Control} control Control whose CSS classes are to be + * returned. + * @return {!Array.<string>} Array of CSS class names applicable to the control. + * @protected + */ +goog.ui.ControlRenderer.prototype.getClassNames = function(control) { + var cssClass = this.getCssClass(); + + // Start with the renderer-specific class name. + var classNames = [cssClass]; + + // Add structural class name, if different. + var structuralCssClass = this.getStructuralCssClass(); + if (structuralCssClass != cssClass) { + classNames.push(structuralCssClass); + } + + // Add state-specific class names, if any. + var classNamesForState = this.getClassNamesForState(control.getState()); + classNames.push.apply(classNames, classNamesForState); + + // Add extra class names, if any. + var extraClassNames = control.getExtraClassNames(); + if (extraClassNames) { + classNames.push.apply(classNames, extraClassNames); + } + + // Add composite classes for IE6 support + if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('7')) { + classNames.push.apply(classNames, + this.getAppliedCombinedClassNames_(classNames)); + } + + return classNames; +}; + + +/** + * Returns an array of all the combined class names that should be applied based + * on the given list of classes. Checks the result of + * {@link getIe6ClassCombinations} for any combinations that have all + * members contained in classes. If a combination matches, the members are + * joined with an underscore (in order), and added to the return array. + * + * If opt_includedClass is provided, return only the combined classes that have + * all members contained in classes AND include opt_includedClass as well. + * opt_includedClass is added to classes as well. + * @param {goog.array.ArrayLike.<string>} classes Array-like thing of classes to + * return matching combined classes for. + * @param {?string=} opt_includedClass If provided, get only the combined + * classes that include this one. + * @return {!Array.<string>} Array of combined class names that should be + * applied. + * @private + */ +goog.ui.ControlRenderer.prototype.getAppliedCombinedClassNames_ = function( + classes, opt_includedClass) { + var toAdd = []; + if (opt_includedClass) { + classes = classes.concat([opt_includedClass]); + } + goog.array.forEach(this.getIe6ClassCombinations(), function(combo) { + if (goog.array.every(combo, goog.partial(goog.array.contains, classes)) && + (!opt_includedClass || goog.array.contains(combo, opt_includedClass))) { + toAdd.push(combo.join('_')); + } + }); + return toAdd; +}; + + +/** + * Takes a bit mask of {@link goog.ui.Component.State}s, and returns an array + * of the appropriate class names representing the given state, suitable to be + * applied to the root element of a component rendered using this renderer, or + * null if no state-specific classes need to be applied. This default + * implementation uses the renderer's {@link getClassForState} method to + * generate each state-specific class. + * @param {number} state Bit mask of component states. + * @return {!Array.<string>} Array of CSS class names representing the given + * state. + * @protected + */ +goog.ui.ControlRenderer.prototype.getClassNamesForState = function(state) { + var classNames = []; + while (state) { + // For each enabled state, push the corresponding CSS class name onto + // the classNames array. + var mask = state & -state; // Least significant bit + classNames.push(this.getClassForState( + /** @type {goog.ui.Component.State} */ (mask))); + state &= ~mask; + } + return classNames; +}; + + +/** + * Takes a single {@link goog.ui.Component.State}, and returns the + * corresponding CSS class name (null if none). + * @param {goog.ui.Component.State} state Component state. + * @return {string|undefined} CSS class representing the given state (undefined + * if none). + * @protected + */ +goog.ui.ControlRenderer.prototype.getClassForState = function(state) { + if (!this.classByState_) { + this.createClassByStateMap_(); + } + return this.classByState_[state]; +}; + + +/** + * Takes a single CSS class name which may represent a component state, and + * returns the corresponding component state (0x00 if none). + * @param {string} className CSS class name, possibly representing a component + * state. + * @return {goog.ui.Component.State} state Component state corresponding + * to the given CSS class (0x00 if none). + * @protected + */ +goog.ui.ControlRenderer.prototype.getStateFromClass = function(className) { + if (!this.stateByClass_) { + this.createStateByClassMap_(); + } + var state = parseInt(this.stateByClass_[className], 10); + return /** @type {goog.ui.Component.State} */ (isNaN(state) ? 0x00 : state); +}; + + +/** + * Creates the lookup table of states to classes, used during state changes. + * @private + */ +goog.ui.ControlRenderer.prototype.createClassByStateMap_ = function() { + var baseClass = this.getStructuralCssClass(); + + // This ensures space-separated css classnames are not allowed, which some + // ControlRenderers had been doing. See http://b/13694665. + var isValidClassName = !goog.string.contains( + goog.string.normalizeWhitespace(baseClass), ' '); + goog.asserts.assert(isValidClassName, + 'ControlRenderer has an invalid css class: \'' + baseClass + '\''); + + /** + * Map of component states to state-specific structural class names, + * used when changing the DOM in response to a state change. Precomputed + * and cached on first use to minimize object allocations and string + * concatenation. + * @type {Object} + * @private + */ + this.classByState_ = goog.object.create( + goog.ui.Component.State.DISABLED, goog.getCssName(baseClass, 'disabled'), + goog.ui.Component.State.HOVER, goog.getCssName(baseClass, 'hover'), + goog.ui.Component.State.ACTIVE, goog.getCssName(baseClass, 'active'), + goog.ui.Component.State.SELECTED, goog.getCssName(baseClass, 'selected'), + goog.ui.Component.State.CHECKED, goog.getCssName(baseClass, 'checked'), + goog.ui.Component.State.FOCUSED, goog.getCssName(baseClass, 'focused'), + goog.ui.Component.State.OPENED, goog.getCssName(baseClass, 'open')); +}; + + +/** + * Creates the lookup table of classes to states, used during decoration. + * @private + */ +goog.ui.ControlRenderer.prototype.createStateByClassMap_ = function() { + // We need the classByState_ map so we can transpose it. + if (!this.classByState_) { + this.createClassByStateMap_(); + } + + /** + * Map of state-specific structural class names to component states, + * used during element decoration. Precomputed and cached on first use + * to minimize object allocations and string concatenation. + * @type {Object} + * @private + */ + this.stateByClass_ = goog.object.transpose(this.classByState_); +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/decorate.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/decorate.js new file mode 100644 index 0000000..0cf9c2d --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/decorate.js @@ -0,0 +1,38 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provides a function that decorates an element based on its CSS + * class name. + * @author attila@google.com (Attila Bodis) + */ + +goog.provide('goog.ui.decorate'); + +goog.require('goog.ui.registry'); + + +/** + * Decorates the element with a suitable {@link goog.ui.Component} instance, if + * a matching decorator is found. + * @param {Element} element Element to decorate. + * @return {goog.ui.Component?} New component instance, decorating the element. + */ +goog.ui.decorate = function(element) { + var decorator = goog.ui.registry.getDecorator(element); + if (decorator) { + decorator.decorate(element); + } + return decorator; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/idgenerator.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/idgenerator.js new file mode 100644 index 0000000..c018a385 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/idgenerator.js @@ -0,0 +1,48 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Generator for unique element IDs. + * + */ + +goog.provide('goog.ui.IdGenerator'); + + + +/** + * Creates a new id generator. + * @constructor + * @final + */ +goog.ui.IdGenerator = function() { +}; +goog.addSingletonGetter(goog.ui.IdGenerator); + + +/** + * Next unique ID to use + * @type {number} + * @private + */ +goog.ui.IdGenerator.prototype.nextId_ = 0; + + +/** + * Gets the next unique ID. + * @return {string} The next unique identifier. + */ +goog.ui.IdGenerator.prototype.getNextUniqueId = function() { + return ':' + (this.nextId_++).toString(36); +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/menuseparatorrenderer.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/menuseparatorrenderer.js new file mode 100644 index 0000000..7e4dd5a --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/menuseparatorrenderer.js @@ -0,0 +1,113 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Renderer for {@link goog.ui.MenuSeparator}s. + * + * @author attila@google.com (Attila Bodis) + */ + +goog.provide('goog.ui.MenuSeparatorRenderer'); + +goog.require('goog.dom'); +goog.require('goog.dom.classes'); +goog.require('goog.ui.ControlContent'); +goog.require('goog.ui.ControlRenderer'); + + + +/** + * Renderer for menu separators. + * @constructor + * @extends {goog.ui.ControlRenderer} + */ +goog.ui.MenuSeparatorRenderer = function() { + goog.ui.ControlRenderer.call(this); +}; +goog.inherits(goog.ui.MenuSeparatorRenderer, goog.ui.ControlRenderer); +goog.addSingletonGetter(goog.ui.MenuSeparatorRenderer); + + +/** + * Default CSS class to be applied to the root element of components rendered + * by this renderer. + * @type {string} + */ +goog.ui.MenuSeparatorRenderer.CSS_CLASS = goog.getCssName('goog-menuseparator'); + + +/** + * Returns an empty, styled menu separator DIV. Overrides {@link + * goog.ui.ControlRenderer#createDom}. + * @param {goog.ui.Control} separator goog.ui.Separator to render. + * @return {Element} Root element for the separator. + * @override + */ +goog.ui.MenuSeparatorRenderer.prototype.createDom = function(separator) { + return separator.getDomHelper().createDom('div', this.getCssClass()); +}; + + +/** + * Takes an existing element, and decorates it with the separator. Overrides + * {@link goog.ui.ControlRenderer#decorate}. + * @param {goog.ui.Control} separator goog.ui.MenuSeparator to decorate the + * element. + * @param {Element} element Element to decorate. + * @return {Element} Decorated element. + * @override + */ +goog.ui.MenuSeparatorRenderer.prototype.decorate = function(separator, + element) { + // Normally handled in the superclass. But we don't call the superclass. + if (element.id) { + separator.setId(element.id); + } + + if (element.tagName == 'HR') { + // Replace HR with separator. + var hr = element; + element = this.createDom(separator); + goog.dom.insertSiblingBefore(element, hr); + goog.dom.removeNode(hr); + } else { + goog.dom.classes.add(element, this.getCssClass()); + } + return element; +}; + + +/** + * Overrides {@link goog.ui.ControlRenderer#setContent} to do nothing, since + * separators are empty. + * @param {Element} separator The separator's root element. + * @param {goog.ui.ControlContent} content Text caption or DOM structure to be + * set as the separators's content (ignored). + * @override + */ +goog.ui.MenuSeparatorRenderer.prototype.setContent = function(separator, + content) { + // Do nothing. Separators are empty. +}; + + +/** + * Returns the CSS class to be applied to the root element of components + * rendered using this renderer. + * @return {string} Renderer-specific CSS class. + * @override + */ +goog.ui.MenuSeparatorRenderer.prototype.getCssClass = function() { + return goog.ui.MenuSeparatorRenderer.CSS_CLASS; +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/registry.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/registry.js new file mode 100644 index 0000000..91257dc --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/registry.js @@ -0,0 +1,172 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Global renderer and decorator registry. + * @author attila@google.com (Attila Bodis) + */ + +goog.provide('goog.ui.registry'); + +goog.require('goog.asserts'); +goog.require('goog.dom.classlist'); + + +/** + * Given a {@link goog.ui.Component} constructor, returns an instance of its + * default renderer. If the default renderer is a singleton, returns the + * singleton instance; otherwise returns a new instance of the renderer class. + * @param {Function} componentCtor Component constructor function (for example + * {@code goog.ui.Button}). + * @return {goog.ui.ControlRenderer?} Renderer instance (for example the + * singleton instance of {@code goog.ui.ButtonRenderer}), or null if + * no default renderer was found. + */ +goog.ui.registry.getDefaultRenderer = function(componentCtor) { + // Locate the default renderer based on the constructor's unique ID. If no + // renderer is registered for this class, walk up the superClass_ chain. + var key; + /** @type {Function|undefined} */ var rendererCtor; + while (componentCtor) { + key = goog.getUid(componentCtor); + if ((rendererCtor = goog.ui.registry.defaultRenderers_[key])) { + break; + } + componentCtor = componentCtor.superClass_ ? + componentCtor.superClass_.constructor : null; + } + + // If the renderer has a static getInstance method, return the singleton + // instance; otherwise create and return a new instance. + if (rendererCtor) { + return goog.isFunction(rendererCtor.getInstance) ? + rendererCtor.getInstance() : new rendererCtor(); + } + + return null; +}; + + +/** + * Sets the default renderer for the given {@link goog.ui.Component} + * constructor. + * @param {Function} componentCtor Component constructor function (for example + * {@code goog.ui.Button}). + * @param {Function} rendererCtor Renderer constructor function (for example + * {@code goog.ui.ButtonRenderer}). + * @throws {Error} If the arguments aren't functions. + */ +goog.ui.registry.setDefaultRenderer = function(componentCtor, rendererCtor) { + // In this case, explicit validation has negligible overhead (since each + // renderer is only registered once), and helps catch subtle bugs. + if (!goog.isFunction(componentCtor)) { + throw Error('Invalid component class ' + componentCtor); + } + if (!goog.isFunction(rendererCtor)) { + throw Error('Invalid renderer class ' + rendererCtor); + } + + // Map the component constructor's unique ID to the renderer constructor. + var key = goog.getUid(componentCtor); + goog.ui.registry.defaultRenderers_[key] = rendererCtor; +}; + + +/** + * Returns the {@link goog.ui.Component} instance created by the decorator + * factory function registered for the given CSS class name, or null if no + * decorator factory function was found. + * @param {string} className CSS class name. + * @return {goog.ui.Component?} Component instance. + */ +goog.ui.registry.getDecoratorByClassName = function(className) { + return className in goog.ui.registry.decoratorFunctions_ ? + goog.ui.registry.decoratorFunctions_[className]() : null; +}; + + +/** + * Maps a CSS class name to a function that returns a new instance of + * {@link goog.ui.Component} or a subclass, suitable to decorate an element + * that has the specified CSS class. + * @param {string} className CSS class name. + * @param {Function} decoratorFn No-argument function that returns a new + * instance of a {@link goog.ui.Component} to decorate an element. + * @throws {Error} If the class name or the decorator function is invalid. + */ +goog.ui.registry.setDecoratorByClassName = function(className, decoratorFn) { + // In this case, explicit validation has negligible overhead (since each + // decorator is only registered once), and helps catch subtle bugs. + if (!className) { + throw Error('Invalid class name ' + className); + } + if (!goog.isFunction(decoratorFn)) { + throw Error('Invalid decorator function ' + decoratorFn); + } + + goog.ui.registry.decoratorFunctions_[className] = decoratorFn; +}; + + +/** + * Returns an instance of {@link goog.ui.Component} or a subclass suitable to + * decorate the given element, based on its CSS class. + * + * TODO(nnaze): Type of element should be {!Element}. + * + * @param {Element} element Element to decorate. + * @return {goog.ui.Component?} Component to decorate the element (null if + * none). + */ +goog.ui.registry.getDecorator = function(element) { + var decorator; + goog.asserts.assert(element); + var classNames = goog.dom.classlist.get(element); + for (var i = 0, len = classNames.length; i < len; i++) { + if ((decorator = goog.ui.registry.getDecoratorByClassName(classNames[i]))) { + return decorator; + } + } + return null; +}; + + +/** + * Resets the global renderer and decorator registry. + */ +goog.ui.registry.reset = function() { + goog.ui.registry.defaultRenderers_ = {}; + goog.ui.registry.decoratorFunctions_ = {}; +}; + + +/** + * Map of {@link goog.ui.Component} constructor unique IDs to the constructors + * of their default {@link goog.ui.Renderer}s. + * @type {Object} + * @private + */ +goog.ui.registry.defaultRenderers_ = {}; + + +/** + * Map of CSS class names to registry factory functions. The keys are + * class names. The values are function objects that return new instances + * of {@link goog.ui.registry} or one of its subclasses, suitable to + * decorate elements marked with the corresponding CSS class. Used by + * containers while decorating their children. + * @type {Object} + * @private + */ +goog.ui.registry.decoratorFunctions_ = {}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/separator.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/separator.js new file mode 100644 index 0000000..b1ffb69 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/ui/separator.js @@ -0,0 +1,80 @@ +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A class for representing a separator, with renderers for both + * horizontal (menu) and vertical (toolbar) separators. + * + * @author attila@google.com (Attila Bodis) + */ + +goog.provide('goog.ui.Separator'); + +goog.require('goog.a11y.aria'); +goog.require('goog.asserts'); +goog.require('goog.ui.Component.State'); +goog.require('goog.ui.Control'); +goog.require('goog.ui.MenuSeparatorRenderer'); +goog.require('goog.ui.registry'); + + + +/** + * Class representing a separator. Although it extends {@link goog.ui.Control}, + * the Separator class doesn't allocate any event handlers, nor does it change + * its appearance on mouseover, etc. + * @param {goog.ui.MenuSeparatorRenderer=} opt_renderer Renderer to render or + * decorate the separator; defaults to {@link goog.ui.MenuSeparatorRenderer}. + * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, used for + * document interaction. + * @constructor + * @extends {goog.ui.Control} + */ +goog.ui.Separator = function(opt_renderer, opt_domHelper) { + goog.ui.Control.call(this, null, opt_renderer || + goog.ui.MenuSeparatorRenderer.getInstance(), opt_domHelper); + + this.setSupportedState(goog.ui.Component.State.DISABLED, false); + this.setSupportedState(goog.ui.Component.State.HOVER, false); + this.setSupportedState(goog.ui.Component.State.ACTIVE, false); + this.setSupportedState(goog.ui.Component.State.FOCUSED, false); + + // Separators are always considered disabled. + this.setStateInternal(goog.ui.Component.State.DISABLED); +}; +goog.inherits(goog.ui.Separator, goog.ui.Control); + + +/** + * Configures the component after its DOM has been rendered. Overrides + * {@link goog.ui.Control#enterDocument} by making sure no event handler + * is allocated. + * @override + */ +goog.ui.Separator.prototype.enterDocument = function() { + goog.ui.Separator.superClass_.enterDocument.call(this); + var element = this.getElement(); + goog.asserts.assert(element, + 'The DOM element for the separator cannot be null.'); + goog.a11y.aria.setRole(element, 'separator'); +}; + + +// Register a decorator factory function for goog.ui.MenuSeparators. +goog.ui.registry.setDecoratorByClassName( + goog.ui.MenuSeparatorRenderer.CSS_CLASS, + function() { + // Separator defaults to using MenuSeparatorRenderer. + return new goog.ui.Separator(); + }); diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/uri/utils.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/uri/utils.js new file mode 100644 index 0000000..425661e --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/uri/utils.js @@ -0,0 +1,1076 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Simple utilities for dealing with URI strings. + * + * This is intended to be a lightweight alternative to constructing goog.Uri + * objects. Whereas goog.Uri adds several kilobytes to the binary regardless + * of how much of its functionality you use, this is designed to be a set of + * mostly-independent utilities so that the compiler includes only what is + * necessary for the task. Estimated savings of porting is 5k pre-gzip and + * 1.5k post-gzip. To ensure the savings remain, future developers should + * avoid adding new functionality to existing functions, but instead create + * new ones and factor out shared code. + * + * Many of these utilities have limited functionality, tailored to common + * cases. The query parameter utilities assume that the parameter keys are + * already encoded, since most keys are compile-time alphanumeric strings. The + * query parameter mutation utilities also do not tolerate fragment identifiers. + * + * By design, these functions can be slower than goog.Uri equivalents. + * Repeated calls to some of functions may be quadratic in behavior for IE, + * although the effect is somewhat limited given the 2kb limit. + * + * One advantage of the limited functionality here is that this approach is + * less sensitive to differences in URI encodings than goog.Uri, since these + * functions modify the strings in place, rather than decoding and + * re-encoding. + * + * Uses features of RFC 3986 for parsing/formatting URIs: + * http://www.ietf.org/rfc/rfc3986.txt + * + * @author gboyer@google.com (Garrett Boyer) - The "lightened" design. + * @author msamuel@google.com (Mike Samuel) - Domain knowledge and regexes. + */ + +goog.provide('goog.uri.utils'); +goog.provide('goog.uri.utils.ComponentIndex'); +goog.provide('goog.uri.utils.QueryArray'); +goog.provide('goog.uri.utils.QueryValue'); +goog.provide('goog.uri.utils.StandardQueryParam'); + +goog.require('goog.asserts'); +goog.require('goog.string'); +goog.require('goog.userAgent'); + + +/** + * Character codes inlined to avoid object allocations due to charCode. + * @enum {number} + * @private + */ +goog.uri.utils.CharCode_ = { + AMPERSAND: 38, + EQUAL: 61, + HASH: 35, + QUESTION: 63 +}; + + +/** + * Builds a URI string from already-encoded parts. + * + * No encoding is performed. Any component may be omitted as either null or + * undefined. + * + * @param {?string=} opt_scheme The scheme such as 'http'. + * @param {?string=} opt_userInfo The user name before the '@'. + * @param {?string=} opt_domain The domain such as 'www.google.com', already + * URI-encoded. + * @param {(string|number|null)=} opt_port The port number. + * @param {?string=} opt_path The path, already URI-encoded. If it is not + * empty, it must begin with a slash. + * @param {?string=} opt_queryData The URI-encoded query data. + * @param {?string=} opt_fragment The URI-encoded fragment identifier. + * @return {string} The fully combined URI. + */ +goog.uri.utils.buildFromEncodedParts = function(opt_scheme, opt_userInfo, + opt_domain, opt_port, opt_path, opt_queryData, opt_fragment) { + var out = ''; + + if (opt_scheme) { + out += opt_scheme + ':'; + } + + if (opt_domain) { + out += '//'; + + if (opt_userInfo) { + out += opt_userInfo + '@'; + } + + out += opt_domain; + + if (opt_port) { + out += ':' + opt_port; + } + } + + if (opt_path) { + out += opt_path; + } + + if (opt_queryData) { + out += '?' + opt_queryData; + } + + if (opt_fragment) { + out += '#' + opt_fragment; + } + + return out; +}; + + +/** + * A regular expression for breaking a URI into its component parts. + * + * {@link http://www.ietf.org/rfc/rfc3986.txt} says in Appendix B + * As the "first-match-wins" algorithm is identical to the "greedy" + * disambiguation method used by POSIX regular expressions, it is natural and + * commonplace to use a regular expression for parsing the potential five + * components of a URI reference. + * + * The following line is the regular expression for breaking-down a + * well-formed URI reference into its components. + * + * <pre> + * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? + * 12 3 4 5 6 7 8 9 + * </pre> + * + * The numbers in the second line above are only to assist readability; they + * indicate the reference points for each subexpression (i.e., each paired + * parenthesis). We refer to the value matched for subexpression <n> as $<n>. + * For example, matching the above expression to + * <pre> + * http://www.ics.uci.edu/pub/ietf/uri/#Related + * </pre> + * results in the following subexpression matches: + * <pre> + * $1 = http: + * $2 = http + * $3 = //www.ics.uci.edu + * $4 = www.ics.uci.edu + * $5 = /pub/ietf/uri/ + * $6 = <undefined> + * $7 = <undefined> + * $8 = #Related + * $9 = Related + * </pre> + * where <undefined> indicates that the component is not present, as is the + * case for the query component in the above example. Therefore, we can + * determine the value of the five components as + * <pre> + * scheme = $2 + * authority = $4 + * path = $5 + * query = $7 + * fragment = $9 + * </pre> + * + * The regular expression has been modified slightly to expose the + * userInfo, domain, and port separately from the authority. + * The modified version yields + * <pre> + * $1 = http scheme + * $2 = <undefined> userInfo -\ + * $3 = www.ics.uci.edu domain | authority + * $4 = <undefined> port -/ + * $5 = /pub/ietf/uri/ path + * $6 = <undefined> query without ? + * $7 = Related fragment without # + * </pre> + * @type {!RegExp} + * @private + */ +goog.uri.utils.splitRe_ = new RegExp( + '^' + + '(?:' + + '([^:/?#.]+)' + // scheme - ignore special characters + // used by other URL parts such as :, + // ?, /, #, and . + ':)?' + + '(?://' + + '(?:([^/?#]*)@)?' + // userInfo + '([^/#?]*?)' + // domain + '(?::([0-9]+))?' + // port + '(?=[/#?]|$)' + // authority-terminating character + ')?' + + '([^?#]+)?' + // path + '(?:\\?([^#]*))?' + // query + '(?:#(.*))?' + // fragment + '$'); + + +/** + * The index of each URI component in the return value of goog.uri.utils.split. + * @enum {number} + */ +goog.uri.utils.ComponentIndex = { + SCHEME: 1, + USER_INFO: 2, + DOMAIN: 3, + PORT: 4, + PATH: 5, + QUERY_DATA: 6, + FRAGMENT: 7 +}; + + +/** + * Splits a URI into its component parts. + * + * Each component can be accessed via the component indices; for example: + * <pre> + * goog.uri.utils.split(someStr)[goog.uri.utils.CompontentIndex.QUERY_DATA]; + * </pre> + * + * @param {string} uri The URI string to examine. + * @return {!Array.<string|undefined>} Each component still URI-encoded. + * Each component that is present will contain the encoded value, whereas + * components that are not present will be undefined or empty, depending + * on the browser's regular expression implementation. Never null, since + * arbitrary strings may still look like path names. + */ +goog.uri.utils.split = function(uri) { + goog.uri.utils.phishingProtection_(); + + // See @return comment -- never null. + return /** @type {!Array.<string|undefined>} */ ( + uri.match(goog.uri.utils.splitRe_)); +}; + + +/** + * Safari has a nasty bug where if you have an http URL with a username, e.g., + * http://evil.com%2F@google.com/ + * Safari will report that window.location.href is + * http://evil.com/google.com/ + * so that anyone who tries to parse the domain of that URL will get + * the wrong domain. We've seen exploits where people use this to trick + * Safari into loading resources from evil domains. + * + * To work around this, we run a little "Safari phishing check", and throw + * an exception if we see this happening. + * + * There is no convenient place to put this check. We apply it to + * anyone doing URI parsing on Webkit. We're not happy about this, but + * it fixes the problem. + * + * This should be removed once Safari fixes their bug. + * + * Exploit reported by Masato Kinugawa. + * + * @type {boolean} + * @private + */ +goog.uri.utils.needsPhishingProtection_ = goog.userAgent.WEBKIT; + + +/** + * Check to see if the user is being phished. + * @private + */ +goog.uri.utils.phishingProtection_ = function() { + if (goog.uri.utils.needsPhishingProtection_) { + // Turn protection off, so that we don't recurse. + goog.uri.utils.needsPhishingProtection_ = false; + + // Use quoted access, just in case the user isn't using location externs. + var location = goog.global['location']; + if (location) { + var href = location['href']; + if (href) { + var domain = goog.uri.utils.getDomain(href); + if (domain && domain != location['hostname']) { + // Phishing attack + goog.uri.utils.needsPhishingProtection_ = true; + throw Error(); + } + } + } + } +}; + + +/** + * @param {?string} uri A possibly null string. + * @return {?string} The string URI-decoded, or null if uri is null. + * @private + */ +goog.uri.utils.decodeIfPossible_ = function(uri) { + return uri && decodeURIComponent(uri); +}; + + +/** + * Gets a URI component by index. + * + * It is preferred to use the getPathEncoded() variety of functions ahead, + * since they are more readable. + * + * @param {goog.uri.utils.ComponentIndex} componentIndex The component index. + * @param {string} uri The URI to examine. + * @return {?string} The still-encoded component, or null if the component + * is not present. + * @private + */ +goog.uri.utils.getComponentByIndex_ = function(componentIndex, uri) { + // Convert undefined, null, and empty string into null. + return goog.uri.utils.split(uri)[componentIndex] || null; +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?string} The protocol or scheme, or null if none. Does not + * include trailing colons or slashes. + */ +goog.uri.utils.getScheme = function(uri) { + return goog.uri.utils.getComponentByIndex_( + goog.uri.utils.ComponentIndex.SCHEME, uri); +}; + + +/** + * Gets the effective scheme for the URL. If the URL is relative then the + * scheme is derived from the page's location. + * @param {string} uri The URI to examine. + * @return {string} The protocol or scheme, always lower case. + */ +goog.uri.utils.getEffectiveScheme = function(uri) { + var scheme = goog.uri.utils.getScheme(uri); + if (!scheme && self.location) { + var protocol = self.location.protocol; + scheme = protocol.substr(0, protocol.length - 1); + } + // NOTE: When called from a web worker in Firefox 3.5, location maybe null. + // All other browsers with web workers support self.location from the worker. + return scheme ? scheme.toLowerCase() : ''; +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?string} The user name still encoded, or null if none. + */ +goog.uri.utils.getUserInfoEncoded = function(uri) { + return goog.uri.utils.getComponentByIndex_( + goog.uri.utils.ComponentIndex.USER_INFO, uri); +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?string} The decoded user info, or null if none. + */ +goog.uri.utils.getUserInfo = function(uri) { + return goog.uri.utils.decodeIfPossible_( + goog.uri.utils.getUserInfoEncoded(uri)); +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?string} The domain name still encoded, or null if none. + */ +goog.uri.utils.getDomainEncoded = function(uri) { + return goog.uri.utils.getComponentByIndex_( + goog.uri.utils.ComponentIndex.DOMAIN, uri); +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?string} The decoded domain, or null if none. + */ +goog.uri.utils.getDomain = function(uri) { + return goog.uri.utils.decodeIfPossible_(goog.uri.utils.getDomainEncoded(uri)); +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?number} The port number, or null if none. + */ +goog.uri.utils.getPort = function(uri) { + // Coerce to a number. If the result of getComponentByIndex_ is null or + // non-numeric, the number coersion yields NaN. This will then return + // null for all non-numeric cases (though also zero, which isn't a relevant + // port number). + return Number(goog.uri.utils.getComponentByIndex_( + goog.uri.utils.ComponentIndex.PORT, uri)) || null; +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?string} The path still encoded, or null if none. Includes the + * leading slash, if any. + */ +goog.uri.utils.getPathEncoded = function(uri) { + return goog.uri.utils.getComponentByIndex_( + goog.uri.utils.ComponentIndex.PATH, uri); +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?string} The decoded path, or null if none. Includes the leading + * slash, if any. + */ +goog.uri.utils.getPath = function(uri) { + return goog.uri.utils.decodeIfPossible_(goog.uri.utils.getPathEncoded(uri)); +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?string} The query data still encoded, or null if none. Does not + * include the question mark itself. + */ +goog.uri.utils.getQueryData = function(uri) { + return goog.uri.utils.getComponentByIndex_( + goog.uri.utils.ComponentIndex.QUERY_DATA, uri); +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?string} The fragment identifier, or null if none. Does not + * include the hash mark itself. + */ +goog.uri.utils.getFragmentEncoded = function(uri) { + // The hash mark may not appear in any other part of the URL. + var hashIndex = uri.indexOf('#'); + return hashIndex < 0 ? null : uri.substr(hashIndex + 1); +}; + + +/** + * @param {string} uri The URI to examine. + * @param {?string} fragment The encoded fragment identifier, or null if none. + * Does not include the hash mark itself. + * @return {string} The URI with the fragment set. + */ +goog.uri.utils.setFragmentEncoded = function(uri, fragment) { + return goog.uri.utils.removeFragment(uri) + (fragment ? '#' + fragment : ''); +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?string} The decoded fragment identifier, or null if none. Does + * not include the hash mark. + */ +goog.uri.utils.getFragment = function(uri) { + return goog.uri.utils.decodeIfPossible_( + goog.uri.utils.getFragmentEncoded(uri)); +}; + + +/** + * Extracts everything up to the port of the URI. + * @param {string} uri The URI string. + * @return {string} Everything up to and including the port. + */ +goog.uri.utils.getHost = function(uri) { + var pieces = goog.uri.utils.split(uri); + return goog.uri.utils.buildFromEncodedParts( + pieces[goog.uri.utils.ComponentIndex.SCHEME], + pieces[goog.uri.utils.ComponentIndex.USER_INFO], + pieces[goog.uri.utils.ComponentIndex.DOMAIN], + pieces[goog.uri.utils.ComponentIndex.PORT]); +}; + + +/** + * Extracts the path of the URL and everything after. + * @param {string} uri The URI string. + * @return {string} The URI, starting at the path and including the query + * parameters and fragment identifier. + */ +goog.uri.utils.getPathAndAfter = function(uri) { + var pieces = goog.uri.utils.split(uri); + return goog.uri.utils.buildFromEncodedParts(null, null, null, null, + pieces[goog.uri.utils.ComponentIndex.PATH], + pieces[goog.uri.utils.ComponentIndex.QUERY_DATA], + pieces[goog.uri.utils.ComponentIndex.FRAGMENT]); +}; + + +/** + * Gets the URI with the fragment identifier removed. + * @param {string} uri The URI to examine. + * @return {string} Everything preceding the hash mark. + */ +goog.uri.utils.removeFragment = function(uri) { + // The hash mark may not appear in any other part of the URL. + var hashIndex = uri.indexOf('#'); + return hashIndex < 0 ? uri : uri.substr(0, hashIndex); +}; + + +/** + * Ensures that two URI's have the exact same domain, scheme, and port. + * + * Unlike the version in goog.Uri, this checks protocol, and therefore is + * suitable for checking against the browser's same-origin policy. + * + * @param {string} uri1 The first URI. + * @param {string} uri2 The second URI. + * @return {boolean} Whether they have the same domain and port. + */ +goog.uri.utils.haveSameDomain = function(uri1, uri2) { + var pieces1 = goog.uri.utils.split(uri1); + var pieces2 = goog.uri.utils.split(uri2); + return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] == + pieces2[goog.uri.utils.ComponentIndex.DOMAIN] && + pieces1[goog.uri.utils.ComponentIndex.SCHEME] == + pieces2[goog.uri.utils.ComponentIndex.SCHEME] && + pieces1[goog.uri.utils.ComponentIndex.PORT] == + pieces2[goog.uri.utils.ComponentIndex.PORT]; +}; + + +/** + * Asserts that there are no fragment or query identifiers, only in uncompiled + * mode. + * @param {string} uri The URI to examine. + * @private + */ +goog.uri.utils.assertNoFragmentsOrQueries_ = function(uri) { + // NOTE: would use goog.asserts here, but jscompiler doesn't know that + // indexOf has no side effects. + if (goog.DEBUG && (uri.indexOf('#') >= 0 || uri.indexOf('?') >= 0)) { + throw Error('goog.uri.utils: Fragment or query identifiers are not ' + + 'supported: [' + uri + ']'); + } +}; + + +/** + * Supported query parameter values by the parameter serializing utilities. + * + * If a value is null or undefined, the key-value pair is skipped, as an easy + * way to omit parameters conditionally. Non-array parameters are converted + * to a string and URI encoded. Array values are expanded into multiple + * &key=value pairs, with each element stringized and URI-encoded. + * + * @typedef {*} + */ +goog.uri.utils.QueryValue; + + +/** + * An array representing a set of query parameters with alternating keys + * and values. + * + * Keys are assumed to be URI encoded already and live at even indices. See + * goog.uri.utils.QueryValue for details on how parameter values are encoded. + * + * Example: + * <pre> + * var data = [ + * // Simple param: ?name=BobBarker + * 'name', 'BobBarker', + * // Conditional param -- may be omitted entirely. + * 'specialDietaryNeeds', hasDietaryNeeds() ? getDietaryNeeds() : null, + * // Multi-valued param: &house=LosAngeles&house=NewYork&house=null + * 'house', ['LosAngeles', 'NewYork', null] + * ]; + * </pre> + * + * @typedef {!Array.<string|goog.uri.utils.QueryValue>} + */ +goog.uri.utils.QueryArray; + + +/** + * Appends a URI and query data in a string buffer with special preconditions. + * + * Internal implementation utility, performing very few object allocations. + * + * @param {!Array.<string|undefined>} buffer A string buffer. The first element + * must be the base URI, and may have a fragment identifier. If the array + * contains more than one element, the second element must be an ampersand, + * and may be overwritten, depending on the base URI. Undefined elements + * are treated as empty-string. + * @return {string} The concatenated URI and query data. + * @private + */ +goog.uri.utils.appendQueryData_ = function(buffer) { + if (buffer[1]) { + // At least one query parameter was added. We need to check the + // punctuation mark, which is currently an ampersand, and also make sure + // there aren't any interfering fragment identifiers. + var baseUri = /** @type {string} */ (buffer[0]); + var hashIndex = baseUri.indexOf('#'); + if (hashIndex >= 0) { + // Move the fragment off the base part of the URI into the end. + buffer.push(baseUri.substr(hashIndex)); + buffer[0] = baseUri = baseUri.substr(0, hashIndex); + } + var questionIndex = baseUri.indexOf('?'); + if (questionIndex < 0) { + // No question mark, so we need a question mark instead of an ampersand. + buffer[1] = '?'; + } else if (questionIndex == baseUri.length - 1) { + // Question mark is the very last character of the existing URI, so don't + // append an additional delimiter. + buffer[1] = undefined; + } + } + + return buffer.join(''); +}; + + +/** + * Appends key=value pairs to an array, supporting multi-valued objects. + * @param {string} key The key prefix. + * @param {goog.uri.utils.QueryValue} value The value to serialize. + * @param {!Array.<string>} pairs The array to which the 'key=value' strings + * should be appended. + * @private + */ +goog.uri.utils.appendKeyValuePairs_ = function(key, value, pairs) { + if (goog.isArray(value)) { + // Convince the compiler it's an array. + goog.asserts.assertArray(value); + for (var j = 0; j < value.length; j++) { + // Convert to string explicitly, to short circuit the null and array + // logic in this function -- this ensures that null and undefined get + // written as literal 'null' and 'undefined', and arrays don't get + // expanded out but instead encoded in the default way. + goog.uri.utils.appendKeyValuePairs_(key, String(value[j]), pairs); + } + } else if (value != null) { + // Skip a top-level null or undefined entirely. + pairs.push('&', key, + // Check for empty string. Zero gets encoded into the url as literal + // strings. For empty string, skip the equal sign, to be consistent + // with UriBuilder.java. + value === '' ? '' : '=', + goog.string.urlEncode(value)); + } +}; + + +/** + * Builds a buffer of query data from a sequence of alternating keys and values. + * + * @param {!Array.<string|undefined>} buffer A string buffer to append to. The + * first element appended will be an '&', and may be replaced by the caller. + * @param {goog.uri.utils.QueryArray|Arguments} keysAndValues An array with + * alternating keys and values -- see the typedef. + * @param {number=} opt_startIndex A start offset into the arary, defaults to 0. + * @return {!Array.<string|undefined>} The buffer argument. + * @private + */ +goog.uri.utils.buildQueryDataBuffer_ = function( + buffer, keysAndValues, opt_startIndex) { + goog.asserts.assert(Math.max(keysAndValues.length - (opt_startIndex || 0), + 0) % 2 == 0, 'goog.uri.utils: Key/value lists must be even in length.'); + + for (var i = opt_startIndex || 0; i < keysAndValues.length; i += 2) { + goog.uri.utils.appendKeyValuePairs_( + keysAndValues[i], keysAndValues[i + 1], buffer); + } + + return buffer; +}; + + +/** + * Builds a query data string from a sequence of alternating keys and values. + * Currently generates "&key&" for empty args. + * + * @param {goog.uri.utils.QueryArray} keysAndValues Alternating keys and + * values. See the typedef. + * @param {number=} opt_startIndex A start offset into the arary, defaults to 0. + * @return {string} The encoded query string, in the form 'a=1&b=2'. + */ +goog.uri.utils.buildQueryData = function(keysAndValues, opt_startIndex) { + var buffer = goog.uri.utils.buildQueryDataBuffer_( + [], keysAndValues, opt_startIndex); + buffer[0] = ''; // Remove the leading ampersand. + return buffer.join(''); +}; + + +/** + * Builds a buffer of query data from a map. + * + * @param {!Array.<string|undefined>} buffer A string buffer to append to. The + * first element appended will be an '&', and may be replaced by the caller. + * @param {Object.<goog.uri.utils.QueryValue>} map An object where keys are + * URI-encoded parameter keys, and the values conform to the contract + * specified in the goog.uri.utils.QueryValue typedef. + * @return {!Array.<string|undefined>} The buffer argument. + * @private + */ +goog.uri.utils.buildQueryDataBufferFromMap_ = function(buffer, map) { + for (var key in map) { + goog.uri.utils.appendKeyValuePairs_(key, map[key], buffer); + } + + return buffer; +}; + + +/** + * Builds a query data string from a map. + * Currently generates "&key&" for empty args. + * + * @param {Object} map An object where keys are URI-encoded parameter keys, + * and the values are arbitrary types or arrays. Keys with a null value + * are dropped. + * @return {string} The encoded query string, in the form 'a=1&b=2'. + */ +goog.uri.utils.buildQueryDataFromMap = function(map) { + var buffer = goog.uri.utils.buildQueryDataBufferFromMap_([], map); + buffer[0] = ''; + return buffer.join(''); +}; + + +/** + * Appends URI parameters to an existing URI. + * + * The variable arguments may contain alternating keys and values. Keys are + * assumed to be already URI encoded. The values should not be URI-encoded, + * and will instead be encoded by this function. + * <pre> + * appendParams('http://www.foo.com?existing=true', + * 'key1', 'value1', + * 'key2', 'value?willBeEncoded', + * 'key3', ['valueA', 'valueB', 'valueC'], + * 'key4', null); + * result: 'http://www.foo.com?existing=true&' + + * 'key1=value1&' + + * 'key2=value%3FwillBeEncoded&' + + * 'key3=valueA&key3=valueB&key3=valueC' + * </pre> + * + * A single call to this function will not exhibit quadratic behavior in IE, + * whereas multiple repeated calls may, although the effect is limited by + * fact that URL's generally can't exceed 2kb. + * + * @param {string} uri The original URI, which may already have query data. + * @param {...(goog.uri.utils.QueryArray|string|goog.uri.utils.QueryValue)} var_args + * An array or argument list conforming to goog.uri.utils.QueryArray. + * @return {string} The URI with all query parameters added. + */ +goog.uri.utils.appendParams = function(uri, var_args) { + return goog.uri.utils.appendQueryData_( + arguments.length == 2 ? + goog.uri.utils.buildQueryDataBuffer_([uri], arguments[1], 0) : + goog.uri.utils.buildQueryDataBuffer_([uri], arguments, 1)); +}; + + +/** + * Appends query parameters from a map. + * + * @param {string} uri The original URI, which may already have query data. + * @param {Object} map An object where keys are URI-encoded parameter keys, + * and the values are arbitrary types or arrays. Keys with a null value + * are dropped. + * @return {string} The new parameters. + */ +goog.uri.utils.appendParamsFromMap = function(uri, map) { + return goog.uri.utils.appendQueryData_( + goog.uri.utils.buildQueryDataBufferFromMap_([uri], map)); +}; + + +/** + * Appends a single URI parameter. + * + * Repeated calls to this can exhibit quadratic behavior in IE6 due to the + * way string append works, though it should be limited given the 2kb limit. + * + * @param {string} uri The original URI, which may already have query data. + * @param {string} key The key, which must already be URI encoded. + * @param {*=} opt_value The value, which will be stringized and encoded + * (assumed not already to be encoded). If omitted, undefined, or null, the + * key will be added as a valueless parameter. + * @return {string} The URI with the query parameter added. + */ +goog.uri.utils.appendParam = function(uri, key, opt_value) { + var paramArr = [uri, '&', key]; + if (goog.isDefAndNotNull(opt_value)) { + paramArr.push('=', goog.string.urlEncode(opt_value)); + } + return goog.uri.utils.appendQueryData_(paramArr); +}; + + +/** + * Finds the next instance of a query parameter with the specified name. + * + * Does not instantiate any objects. + * + * @param {string} uri The URI to search. May contain a fragment identifier + * if opt_hashIndex is specified. + * @param {number} startIndex The index to begin searching for the key at. A + * match may be found even if this is one character after the ampersand. + * @param {string} keyEncoded The URI-encoded key. + * @param {number} hashOrEndIndex Index to stop looking at. If a hash + * mark is present, it should be its index, otherwise it should be the + * length of the string. + * @return {number} The position of the first character in the key's name, + * immediately after either a question mark or a dot. + * @private + */ +goog.uri.utils.findParam_ = function( + uri, startIndex, keyEncoded, hashOrEndIndex) { + var index = startIndex; + var keyLength = keyEncoded.length; + + // Search for the key itself and post-filter for surronuding punctuation, + // rather than expensively building a regexp. + while ((index = uri.indexOf(keyEncoded, index)) >= 0 && + index < hashOrEndIndex) { + var precedingChar = uri.charCodeAt(index - 1); + // Ensure that the preceding character is '&' or '?'. + if (precedingChar == goog.uri.utils.CharCode_.AMPERSAND || + precedingChar == goog.uri.utils.CharCode_.QUESTION) { + // Ensure the following character is '&', '=', '#', or NaN + // (end of string). + var followingChar = uri.charCodeAt(index + keyLength); + if (!followingChar || + followingChar == goog.uri.utils.CharCode_.EQUAL || + followingChar == goog.uri.utils.CharCode_.AMPERSAND || + followingChar == goog.uri.utils.CharCode_.HASH) { + return index; + } + } + index += keyLength + 1; + } + + return -1; +}; + + +/** + * Regular expression for finding a hash mark or end of string. + * @type {RegExp} + * @private + */ +goog.uri.utils.hashOrEndRe_ = /#|$/; + + +/** + * Determines if the URI contains a specific key. + * + * Performs no object instantiations. + * + * @param {string} uri The URI to process. May contain a fragment + * identifier. + * @param {string} keyEncoded The URI-encoded key. Case-sensitive. + * @return {boolean} Whether the key is present. + */ +goog.uri.utils.hasParam = function(uri, keyEncoded) { + return goog.uri.utils.findParam_(uri, 0, keyEncoded, + uri.search(goog.uri.utils.hashOrEndRe_)) >= 0; +}; + + +/** + * Gets the first value of a query parameter. + * @param {string} uri The URI to process. May contain a fragment. + * @param {string} keyEncoded The URI-encoded key. Case-sensitive. + * @return {?string} The first value of the parameter (URI-decoded), or null + * if the parameter is not found. + */ +goog.uri.utils.getParamValue = function(uri, keyEncoded) { + var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_); + var foundIndex = goog.uri.utils.findParam_( + uri, 0, keyEncoded, hashOrEndIndex); + + if (foundIndex < 0) { + return null; + } else { + var endPosition = uri.indexOf('&', foundIndex); + if (endPosition < 0 || endPosition > hashOrEndIndex) { + endPosition = hashOrEndIndex; + } + // Progress forth to the end of the "key=" or "key&" substring. + foundIndex += keyEncoded.length + 1; + // Use substr, because it (unlike substring) will return empty string + // if foundIndex > endPosition. + return goog.string.urlDecode( + uri.substr(foundIndex, endPosition - foundIndex)); + } +}; + + +/** + * Gets all values of a query parameter. + * @param {string} uri The URI to process. May contain a framgnet. + * @param {string} keyEncoded The URI-encoded key. Case-snsitive. + * @return {!Array.<string>} All URI-decoded values with the given key. + * If the key is not found, this will have length 0, but never be null. + */ +goog.uri.utils.getParamValues = function(uri, keyEncoded) { + var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_); + var position = 0; + var foundIndex; + var result = []; + + while ((foundIndex = goog.uri.utils.findParam_( + uri, position, keyEncoded, hashOrEndIndex)) >= 0) { + // Find where this parameter ends, either the '&' or the end of the + // query parameters. + position = uri.indexOf('&', foundIndex); + if (position < 0 || position > hashOrEndIndex) { + position = hashOrEndIndex; + } + + // Progress forth to the end of the "key=" or "key&" substring. + foundIndex += keyEncoded.length + 1; + // Use substr, because it (unlike substring) will return empty string + // if foundIndex > position. + result.push(goog.string.urlDecode(uri.substr( + foundIndex, position - foundIndex))); + } + + return result; +}; + + +/** + * Regexp to find trailing question marks and ampersands. + * @type {RegExp} + * @private + */ +goog.uri.utils.trailingQueryPunctuationRe_ = /[?&]($|#)/; + + +/** + * Removes all instances of a query parameter. + * @param {string} uri The URI to process. Must not contain a fragment. + * @param {string} keyEncoded The URI-encoded key. + * @return {string} The URI with all instances of the parameter removed. + */ +goog.uri.utils.removeParam = function(uri, keyEncoded) { + var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_); + var position = 0; + var foundIndex; + var buffer = []; + + // Look for a query parameter. + while ((foundIndex = goog.uri.utils.findParam_( + uri, position, keyEncoded, hashOrEndIndex)) >= 0) { + // Get the portion of the query string up to, but not including, the ? + // or & starting the parameter. + buffer.push(uri.substring(position, foundIndex)); + // Progress to immediately after the '&'. If not found, go to the end. + // Avoid including the hash mark. + position = Math.min((uri.indexOf('&', foundIndex) + 1) || hashOrEndIndex, + hashOrEndIndex); + } + + // Append everything that is remaining. + buffer.push(uri.substr(position)); + + // Join the buffer, and remove trailing punctuation that remains. + return buffer.join('').replace( + goog.uri.utils.trailingQueryPunctuationRe_, '$1'); +}; + + +/** + * Replaces all existing definitions of a parameter with a single definition. + * + * Repeated calls to this can exhibit quadratic behavior due to the need to + * find existing instances and reconstruct the string, though it should be + * limited given the 2kb limit. Consider using appendParams to append multiple + * parameters in bulk. + * + * @param {string} uri The original URI, which may already have query data. + * @param {string} keyEncoded The key, which must already be URI encoded. + * @param {*} value The value, which will be stringized and encoded (assumed + * not already to be encoded). + * @return {string} The URI with the query parameter added. + */ +goog.uri.utils.setParam = function(uri, keyEncoded, value) { + return goog.uri.utils.appendParam( + goog.uri.utils.removeParam(uri, keyEncoded), keyEncoded, value); +}; + + +/** + * Generates a URI path using a given URI and a path with checks to + * prevent consecutive "//". The baseUri passed in must not contain + * query or fragment identifiers. The path to append may not contain query or + * fragment identifiers. + * + * @param {string} baseUri URI to use as the base. + * @param {string} path Path to append. + * @return {string} Updated URI. + */ +goog.uri.utils.appendPath = function(baseUri, path) { + goog.uri.utils.assertNoFragmentsOrQueries_(baseUri); + + // Remove any trailing '/' + if (goog.string.endsWith(baseUri, '/')) { + baseUri = baseUri.substr(0, baseUri.length - 1); + } + // Remove any leading '/' + if (goog.string.startsWith(path, '/')) { + path = path.substr(1); + } + return goog.string.buildString(baseUri, '/', path); +}; + + +/** + * Replaces the path. + * @param {string} uri URI to use as the base. + * @param {string} path New path. + * @return {string} Updated URI. + */ +goog.uri.utils.setPath = function(uri, path) { + // Add any missing '/'. + if (!goog.string.startsWith(path, '/')) { + path = '/' + path; + } + var parts = goog.uri.utils.split(uri); + return goog.uri.utils.buildFromEncodedParts( + parts[goog.uri.utils.ComponentIndex.SCHEME], + parts[goog.uri.utils.ComponentIndex.USER_INFO], + parts[goog.uri.utils.ComponentIndex.DOMAIN], + parts[goog.uri.utils.ComponentIndex.PORT], + path, + parts[goog.uri.utils.ComponentIndex.QUERY_DATA], + parts[goog.uri.utils.ComponentIndex.FRAGMENT]); +}; + + +/** + * Standard supported query parameters. + * @enum {string} + */ +goog.uri.utils.StandardQueryParam = { + + /** Unused parameter for unique-ifying. */ + RANDOM: 'zx' +}; + + +/** + * Sets the zx parameter of a URI to a random value. + * @param {string} uri Any URI. + * @return {string} That URI with the "zx" parameter added or replaced to + * contain a random string. + */ +goog.uri.utils.makeUnique = function(uri) { + return goog.uri.utils.setParam(uri, + goog.uri.utils.StandardQueryParam.RANDOM, goog.string.getRandomString()); +}; diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/useragent/useragent.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/useragent/useragent.js new file mode 100644 index 0000000..4220217 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/useragent/useragent.js @@ -0,0 +1,553 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Rendering engine detection. + * @see <a href="http://www.useragentstring.com/">User agent strings</a> + * For information on the browser brand (such as Safari versus Chrome), see + * goog.userAgent.product. + * @see ../demos/useragent.html + */ + +goog.provide('goog.userAgent'); + +goog.require('goog.labs.userAgent.browser'); +goog.require('goog.labs.userAgent.engine'); +goog.require('goog.labs.userAgent.util'); +goog.require('goog.string'); + + +/** + * @define {boolean} Whether we know at compile-time that the browser is IE. + */ +goog.define('goog.userAgent.ASSUME_IE', false); + + +/** + * @define {boolean} Whether we know at compile-time that the browser is GECKO. + */ +goog.define('goog.userAgent.ASSUME_GECKO', false); + + +/** + * @define {boolean} Whether we know at compile-time that the browser is WEBKIT. + */ +goog.define('goog.userAgent.ASSUME_WEBKIT', false); + + +/** + * @define {boolean} Whether we know at compile-time that the browser is a + * mobile device running WebKit e.g. iPhone or Android. + */ +goog.define('goog.userAgent.ASSUME_MOBILE_WEBKIT', false); + + +/** + * @define {boolean} Whether we know at compile-time that the browser is OPERA. + */ +goog.define('goog.userAgent.ASSUME_OPERA', false); + + +/** + * @define {boolean} Whether the + * {@code goog.userAgent.isVersionOrHigher} + * function will return true for any version. + */ +goog.define('goog.userAgent.ASSUME_ANY_VERSION', false); + + +/** + * Whether we know the browser engine at compile-time. + * @type {boolean} + * @private + */ +goog.userAgent.BROWSER_KNOWN_ = + goog.userAgent.ASSUME_IE || + goog.userAgent.ASSUME_GECKO || + goog.userAgent.ASSUME_MOBILE_WEBKIT || + goog.userAgent.ASSUME_WEBKIT || + goog.userAgent.ASSUME_OPERA; + + +/** + * Returns the userAgent string for the current browser. + * + * @return {string} The userAgent string. + */ +goog.userAgent.getUserAgentString = function() { + return goog.labs.userAgent.util.getUserAgent(); +}; + + +/** + * TODO(nnaze): Change type to "Navigator" and update compilation targets. + * @return {Object} The native navigator object. + */ +goog.userAgent.getNavigator = function() { + // Need a local navigator reference instead of using the global one, + // to avoid the rare case where they reference different objects. + // (in a WorkerPool, for example). + return goog.global['navigator'] || null; +}; + + +/** + * Whether the user agent is Opera. + * @type {boolean} + */ +goog.userAgent.OPERA = goog.userAgent.BROWSER_KNOWN_ ? + goog.userAgent.ASSUME_OPERA : + goog.labs.userAgent.browser.isOpera(); + + +/** + * Whether the user agent is Internet Explorer. + * @type {boolean} + */ +goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ? + goog.userAgent.ASSUME_IE : + goog.labs.userAgent.browser.isIE(); + + +/** + * Whether the user agent is Gecko. Gecko is the rendering engine used by + * Mozilla, Firefox, and others. + * @type {boolean} + */ +goog.userAgent.GECKO = goog.userAgent.BROWSER_KNOWN_ ? + goog.userAgent.ASSUME_GECKO : + goog.labs.userAgent.engine.isGecko(); + + +/** + * Whether the user agent is WebKit. WebKit is the rendering engine that + * Safari, Android and others use. + * @type {boolean} + */ +goog.userAgent.WEBKIT = goog.userAgent.BROWSER_KNOWN_ ? + goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_MOBILE_WEBKIT : + goog.labs.userAgent.engine.isWebKit(); + + +/** + * Whether the user agent is running on a mobile device. + * + * This is a separate function so that the logic can be tested. + * + * TODO(nnaze): Investigate swapping in goog.labs.userAgent.device.isMobile(). + * + * @return {boolean} Whether the user agent is running on a mobile device. + * @private + */ +goog.userAgent.isMobile_ = function() { + return goog.userAgent.WEBKIT && + goog.labs.userAgent.util.matchUserAgent('Mobile'); +}; + + +/** + * Whether the user agent is running on a mobile device. + * + * TODO(nnaze): Consider deprecating MOBILE when labs.userAgent + * is promoted as the gecko/webkit logic is likely inaccurate. + * + * @type {boolean} + */ +goog.userAgent.MOBILE = goog.userAgent.ASSUME_MOBILE_WEBKIT || + goog.userAgent.isMobile_(); + + +/** + * Used while transitioning code to use WEBKIT instead. + * @type {boolean} + * @deprecated Use {@link goog.userAgent.product.SAFARI} instead. + * TODO(nicksantos): Delete this from goog.userAgent. + */ +goog.userAgent.SAFARI = goog.userAgent.WEBKIT; + + +/** + * @return {string} the platform (operating system) the user agent is running + * on. Default to empty string because navigator.platform may not be defined + * (on Rhino, for example). + * @private + */ +goog.userAgent.determinePlatform_ = function() { + var navigator = goog.userAgent.getNavigator(); + return navigator && navigator.platform || ''; +}; + + +/** + * The platform (operating system) the user agent is running on. Default to + * empty string because navigator.platform may not be defined (on Rhino, for + * example). + * @type {string} + */ +goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_(); + + +/** + * @define {boolean} Whether the user agent is running on a Macintosh operating + * system. + */ +goog.define('goog.userAgent.ASSUME_MAC', false); + + +/** + * @define {boolean} Whether the user agent is running on a Windows operating + * system. + */ +goog.define('goog.userAgent.ASSUME_WINDOWS', false); + + +/** + * @define {boolean} Whether the user agent is running on a Linux operating + * system. + */ +goog.define('goog.userAgent.ASSUME_LINUX', false); + + +/** + * @define {boolean} Whether the user agent is running on a X11 windowing + * system. + */ +goog.define('goog.userAgent.ASSUME_X11', false); + + +/** + * @define {boolean} Whether the user agent is running on Android. + */ +goog.define('goog.userAgent.ASSUME_ANDROID', false); + + +/** + * @define {boolean} Whether the user agent is running on an iPhone. + */ +goog.define('goog.userAgent.ASSUME_IPHONE', false); + + +/** + * @define {boolean} Whether the user agent is running on an iPad. + */ +goog.define('goog.userAgent.ASSUME_IPAD', false); + + +/** + * @type {boolean} + * @private + */ +goog.userAgent.PLATFORM_KNOWN_ = + goog.userAgent.ASSUME_MAC || + goog.userAgent.ASSUME_WINDOWS || + goog.userAgent.ASSUME_LINUX || + goog.userAgent.ASSUME_X11 || + goog.userAgent.ASSUME_ANDROID || + goog.userAgent.ASSUME_IPHONE || + goog.userAgent.ASSUME_IPAD; + + +/** + * Initialize the goog.userAgent constants that define which platform the user + * agent is running on. + * @private + */ +goog.userAgent.initPlatform_ = function() { + /** + * Whether the user agent is running on a Macintosh operating system. + * @type {boolean} + * @private + */ + goog.userAgent.detectedMac_ = goog.string.contains(goog.userAgent.PLATFORM, + 'Mac'); + + /** + * Whether the user agent is running on a Windows operating system. + * @type {boolean} + * @private + */ + goog.userAgent.detectedWindows_ = goog.string.contains( + goog.userAgent.PLATFORM, 'Win'); + + /** + * Whether the user agent is running on a Linux operating system. + * @type {boolean} + * @private + */ + goog.userAgent.detectedLinux_ = goog.string.contains(goog.userAgent.PLATFORM, + 'Linux'); + + /** + * Whether the user agent is running on a X11 windowing system. + * @type {boolean} + * @private + */ + goog.userAgent.detectedX11_ = !!goog.userAgent.getNavigator() && + goog.string.contains(goog.userAgent.getNavigator()['appVersion'] || '', + 'X11'); + + // Need user agent string for Android/IOS detection + var ua = goog.userAgent.getUserAgentString(); + + /** + * Whether the user agent is running on Android. + * @type {boolean} + * @private + */ + goog.userAgent.detectedAndroid_ = !!ua && + goog.string.contains(ua, 'Android'); + + /** + * Whether the user agent is running on an iPhone. + * @type {boolean} + * @private + */ + goog.userAgent.detectedIPhone_ = !!ua && goog.string.contains(ua, 'iPhone'); + + /** + * Whether the user agent is running on an iPad. + * @type {boolean} + * @private + */ + goog.userAgent.detectedIPad_ = !!ua && goog.string.contains(ua, 'iPad'); +}; + + +if (!goog.userAgent.PLATFORM_KNOWN_) { + goog.userAgent.initPlatform_(); +} + + +/** + * Whether the user agent is running on a Macintosh operating system. + * @type {boolean} + */ +goog.userAgent.MAC = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_MAC : goog.userAgent.detectedMac_; + + +/** + * Whether the user agent is running on a Windows operating system. + * @type {boolean} + */ +goog.userAgent.WINDOWS = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_WINDOWS : goog.userAgent.detectedWindows_; + + +/** + * Whether the user agent is running on a Linux operating system. + * @type {boolean} + */ +goog.userAgent.LINUX = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_LINUX : goog.userAgent.detectedLinux_; + + +/** + * Whether the user agent is running on a X11 windowing system. + * @type {boolean} + */ +goog.userAgent.X11 = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_X11 : goog.userAgent.detectedX11_; + + +/** + * Whether the user agent is running on Android. + * @type {boolean} + */ +goog.userAgent.ANDROID = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_ANDROID : goog.userAgent.detectedAndroid_; + + +/** + * Whether the user agent is running on an iPhone. + * @type {boolean} + */ +goog.userAgent.IPHONE = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_IPHONE : goog.userAgent.detectedIPhone_; + + +/** + * Whether the user agent is running on an iPad. + * @type {boolean} + */ +goog.userAgent.IPAD = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_IPAD : goog.userAgent.detectedIPad_; + + +/** + * @return {string} The string that describes the version number of the user + * agent. + * @private + */ +goog.userAgent.determineVersion_ = function() { + // All browsers have different ways to detect the version and they all have + // different naming schemes. + + // version is a string rather than a number because it may contain 'b', 'a', + // and so on. + var version = '', re; + + if (goog.userAgent.OPERA && goog.global['opera']) { + var operaVersion = goog.global['opera'].version; + return goog.isFunction(operaVersion) ? operaVersion() : operaVersion; + } + + if (goog.userAgent.GECKO) { + re = /rv\:([^\);]+)(\)|;)/; + } else if (goog.userAgent.IE) { + re = /\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/; + } else if (goog.userAgent.WEBKIT) { + // WebKit/125.4 + re = /WebKit\/(\S+)/; + } + + if (re) { + var arr = re.exec(goog.userAgent.getUserAgentString()); + version = arr ? arr[1] : ''; + } + + if (goog.userAgent.IE) { + // IE9 can be in document mode 9 but be reporting an inconsistent user agent + // version. If it is identifying as a version lower than 9 we take the + // documentMode as the version instead. IE8 has similar behavior. + // It is recommended to set the X-UA-Compatible header to ensure that IE9 + // uses documentMode 9. + var docMode = goog.userAgent.getDocumentMode_(); + if (docMode > parseFloat(version)) { + return String(docMode); + } + } + + return version; +}; + + +/** + * @return {number|undefined} Returns the document mode (for testing). + * @private + */ +goog.userAgent.getDocumentMode_ = function() { + // NOTE(user): goog.userAgent may be used in context where there is no DOM. + var doc = goog.global['document']; + return doc ? doc['documentMode'] : undefined; +}; + + +/** + * The version of the user agent. This is a string because it might contain + * 'b' (as in beta) as well as multiple dots. + * @type {string} + */ +goog.userAgent.VERSION = goog.userAgent.determineVersion_(); + + +/** + * Compares two version numbers. + * + * @param {string} v1 Version of first item. + * @param {string} v2 Version of second item. + * + * @return {number} 1 if first argument is higher + * 0 if arguments are equal + * -1 if second argument is higher. + * @deprecated Use goog.string.compareVersions. + */ +goog.userAgent.compare = function(v1, v2) { + return goog.string.compareVersions(v1, v2); +}; + + +/** + * Cache for {@link goog.userAgent.isVersionOrHigher}. + * Calls to compareVersions are surprisingly expensive and, as a browser's + * version number is unlikely to change during a session, we cache the results. + * @const + * @private + */ +goog.userAgent.isVersionOrHigherCache_ = {}; + + +/** + * Whether the user agent version is higher or the same as the given version. + * NOTE: When checking the version numbers for Firefox and Safari, be sure to + * use the engine's version, not the browser's version number. For example, + * Firefox 3.0 corresponds to Gecko 1.9 and Safari 3.0 to Webkit 522.11. + * Opera and Internet Explorer versions match the product release number.<br> + * @see <a href="http://en.wikipedia.org/wiki/Safari_version_history"> + * Webkit</a> + * @see <a href="http://en.wikipedia.org/wiki/Gecko_engine">Gecko</a> + * + * @param {string|number} version The version to check. + * @return {boolean} Whether the user agent version is higher or the same as + * the given version. + */ +goog.userAgent.isVersionOrHigher = function(version) { + return goog.userAgent.ASSUME_ANY_VERSION || + goog.userAgent.isVersionOrHigherCache_[version] || + (goog.userAgent.isVersionOrHigherCache_[version] = + goog.string.compareVersions(goog.userAgent.VERSION, version) >= 0); +}; + + +/** + * Deprecated alias to {@code goog.userAgent.isVersionOrHigher}. + * @param {string|number} version The version to check. + * @return {boolean} Whether the user agent version is higher or the same as + * the given version. + * @deprecated Use goog.userAgent.isVersionOrHigher(). + */ +goog.userAgent.isVersion = goog.userAgent.isVersionOrHigher; + + +/** + * Whether the IE effective document mode is higher or the same as the given + * document mode version. + * NOTE: Only for IE, return false for another browser. + * + * @param {number} documentMode The document mode version to check. + * @return {boolean} Whether the IE effective document mode is higher or the + * same as the given version. + */ +goog.userAgent.isDocumentModeOrHigher = function(documentMode) { + return goog.userAgent.IE && goog.userAgent.DOCUMENT_MODE >= documentMode; +}; + + +/** + * Deprecated alias to {@code goog.userAgent.isDocumentModeOrHigher}. + * @param {number} version The version to check. + * @return {boolean} Whether the IE effective document mode is higher or the + * same as the given version. + * @deprecated Use goog.userAgent.isDocumentModeOrHigher(). + */ +goog.userAgent.isDocumentMode = goog.userAgent.isDocumentModeOrHigher; + + +/** + * For IE version < 7, documentMode is undefined, so attempt to use the + * CSS1Compat property to see if we are in standards mode. If we are in + * standards mode, treat the browser version as the document mode. Otherwise, + * IE is emulating version 5. + * @type {number|undefined} + * @const + */ +goog.userAgent.DOCUMENT_MODE = (function() { + var doc = goog.global['document']; + if (!doc || !goog.userAgent.IE) { + return undefined; + } + var mode = goog.userAgent.getDocumentMode_(); + return mode || (doc['compatMode'] == 'CSS1Compat' ? + parseInt(goog.userAgent.VERSION, 10) : 5); +})(); diff --git a/third_party/google_input_tools/third_party/closure_library/third_party/closure/goog/mochikit/async/deferred.js b/third_party/google_input_tools/third_party/closure_library/third_party/closure/goog/mochikit/async/deferred.js new file mode 100644 index 0000000..de32629 --- /dev/null +++ b/third_party/google_input_tools/third_party/closure_library/third_party/closure/goog/mochikit/async/deferred.js @@ -0,0 +1,922 @@ +// Copyright 2007 Bob Ippolito. All Rights Reserved. +// Modifications Copyright 2009 The Closure Library Authors. All Rights +// Reserved. + +/** + * @license Portions of this code are from MochiKit, received by + * The Closure Authors under the MIT license. All other code is Copyright + * 2005-2009 The Closure Authors. All Rights Reserved. + */ + +/** + * @fileoverview Classes for tracking asynchronous operations and handling the + * results. The Deferred object here is patterned after the Deferred object in + * the Twisted python networking framework. + * + * See: http://twistedmatrix.com/projects/core/documentation/howto/defer.html + * + * Based on the Dojo code which in turn is based on the MochiKit code. + * + */ + +goog.provide('goog.async.Deferred'); +goog.provide('goog.async.Deferred.AlreadyCalledError'); +goog.provide('goog.async.Deferred.CanceledError'); + +goog.require('goog.Promise'); +goog.require('goog.Thenable'); +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.debug.Error'); + + + +/** + * A Deferred represents the result of an asynchronous operation. A Deferred + * instance has no result when it is created, and is "fired" (given an initial + * result) by calling {@code callback} or {@code errback}. + * + * Once fired, the result is passed through a sequence of callback functions + * registered with {@code addCallback} or {@code addErrback}. The functions may + * mutate the result before it is passed to the next function in the sequence. + * + * Callbacks and errbacks may be added at any time, including after the Deferred + * has been "fired". If there are no pending actions in the execution sequence + * of a fired Deferred, any new callback functions will be called with the last + * computed result. Adding a callback function is the only way to access the + * result of the Deferred. + * + * If a Deferred operation is canceled, an optional user-provided cancellation + * function is invoked which may perform any special cleanup, followed by firing + * the Deferred's errback sequence with a {@code CanceledError}. If the + * Deferred has already fired, cancellation is ignored. + * + * Deferreds may be templated to a specific type they produce using generics + * with syntax such as: + * <code> + * /** @type {goog.async.Deferred.<string>} */ + * var d = new goog.async.Deferred(); + * // Compiler can infer that foo is a string. + * d.addCallback(function(foo) {...}); + * d.callback('string'); // Checked to be passed a string + * </code> + * Since deferreds are often used to produce different values across a chain, + * the type information is not propagated across chains, but rather only + * associated with specifically cast objects. + * + * @param {Function=} opt_onCancelFunction A function that will be called if the + * Deferred is canceled. If provided, this function runs before the + * Deferred is fired with a {@code CanceledError}. + * @param {Object=} opt_defaultScope The default object context to call + * callbacks and errbacks in. + * @constructor + * @implements {goog.Thenable.<VALUE>} + * @template VALUE + */ +goog.async.Deferred = function(opt_onCancelFunction, opt_defaultScope) { + /** + * Entries in the sequence are arrays containing a callback, an errback, and + * an optional scope. The callback or errback in an entry may be null. + * @type {!Array.<!Array>} + * @private + */ + this.sequence_ = []; + + /** + * Optional function that will be called if the Deferred is canceled. + * @type {Function|undefined} + * @private + */ + this.onCancelFunction_ = opt_onCancelFunction; + + /** + * The default scope to execute callbacks and errbacks in. + * @type {Object} + * @private + */ + this.defaultScope_ = opt_defaultScope || null; + + /** + * Whether the Deferred has been fired. + * @type {boolean} + * @private + */ + this.fired_ = false; + + /** + * Whether the last result in the execution sequence was an error. + * @type {boolean} + * @private + */ + this.hadError_ = false; + + /** + * The current Deferred result, updated as callbacks and errbacks are + * executed. + * @type {*} + * @private + */ + this.result_ = undefined; + + /** + * Whether the Deferred is blocked waiting on another Deferred to fire. If a + * callback or errback returns a Deferred as a result, the execution sequence + * is blocked until that Deferred result becomes available. + * @type {boolean} + * @private + */ + this.blocked_ = false; + + /** + * Whether this Deferred is blocking execution of another Deferred. If this + * instance was returned as a result in another Deferred's execution + * sequence,that other Deferred becomes blocked until this instance's + * execution sequence completes. No additional callbacks may be added to a + * Deferred once it is blocking another instance. + * @type {boolean} + * @private + */ + this.blocking_ = false; + + /** + * Whether the Deferred has been canceled without having a custom cancel + * function. + * @type {boolean} + * @private + */ + this.silentlyCanceled_ = false; + + /** + * If an error is thrown during Deferred execution with no errback to catch + * it, the error is rethrown after a timeout. Reporting the error after a + * timeout allows execution to continue in the calling context (empty when + * no error is scheduled). + * @type {number} + * @private + */ + this.unhandledErrorId_ = 0; + + /** + * If this Deferred was created by branch(), this will be the "parent" + * Deferred. + * @type {goog.async.Deferred} + * @private + */ + this.parent_ = null; + + /** + * The number of Deferred objects that have been branched off this one. This + * will be decremented whenever a branch is fired or canceled. + * @type {number} + * @private + */ + this.branches_ = 0; + + if (goog.async.Deferred.LONG_STACK_TRACES) { + /** + * Holds the stack trace at time of deferred creation if the JS engine + * provides the Error.captureStackTrace API. + * @private {?string} + */ + this.constructorStack_ = null; + if (Error.captureStackTrace) { + var target = { stack: '' }; + Error.captureStackTrace(target, goog.async.Deferred); + // Check if Error.captureStackTrace worked. It fails in gjstest. + if (typeof target.stack == 'string') { + // Remove first line and force stringify to prevent memory leak due to + // holding on to actual stack frames. + this.constructorStack_ = target.stack.replace(/^[^\n]*\n/, ''); + } + } + } +}; + + +/** + * @define {boolean} Whether unhandled errors should always get rethrown to the + * global scope. Defaults to the value of goog.DEBUG. + */ +goog.define('goog.async.Deferred.STRICT_ERRORS', false); + + +/** + * @define {boolean} Whether to attempt to make stack traces long. Defaults to + * the value of goog.DEBUG. + */ +goog.define('goog.async.Deferred.LONG_STACK_TRACES', false); + + +/** + * Cancels a Deferred that has not yet been fired, or is blocked on another + * deferred operation. If this Deferred is waiting for a blocking Deferred to + * fire, the blocking Deferred will also be canceled. + * + * If this Deferred was created by calling branch() on a parent Deferred with + * opt_propagateCancel set to true, the parent may also be canceled. If + * opt_deepCancel is set, cancel() will be called on the parent (as well as any + * other ancestors if the parent is also a branch). If one or more branches were + * created with opt_propagateCancel set to true, the parent will be canceled if + * cancel() is called on all of those branches. + * + * @param {boolean=} opt_deepCancel If true, cancels this Deferred's parent even + * if cancel() hasn't been called on some of the parent's branches. Has no + * effect on a branch without opt_propagateCancel set to true. + */ +goog.async.Deferred.prototype.cancel = function(opt_deepCancel) { + if (!this.hasFired()) { + if (this.parent_) { + // Get rid of the parent reference before potentially running the parent's + // canceler function to ensure that this cancellation isn't + // double-counted. + var parent = this.parent_; + delete this.parent_; + if (opt_deepCancel) { + parent.cancel(opt_deepCancel); + } else { + parent.branchCancel_(); + } + } + + if (this.onCancelFunction_) { + // Call in user-specified scope. + this.onCancelFunction_.call(this.defaultScope_, this); + } else { + this.silentlyCanceled_ = true; + } + if (!this.hasFired()) { + this.errback(new goog.async.Deferred.CanceledError(this)); + } + } else if (this.result_ instanceof goog.async.Deferred) { + this.result_.cancel(); + } +}; + + +/** + * Handle a single branch being canceled. Once all branches are canceled, this + * Deferred will be canceled as well. + * + * @private + */ +goog.async.Deferred.prototype.branchCancel_ = function() { + this.branches_--; + if (this.branches_ <= 0) { + this.cancel(); + } +}; + + +/** + * Called after a blocking Deferred fires. Unblocks this Deferred and resumes + * its execution sequence. + * + * @param {boolean} isSuccess Whether the result is a success or an error. + * @param {*} res The result of the blocking Deferred. + * @private + */ +goog.async.Deferred.prototype.continue_ = function(isSuccess, res) { + this.blocked_ = false; + this.updateResult_(isSuccess, res); +}; + + +/** + * Updates the current result based on the success or failure of the last action + * in the execution sequence. + * + * @param {boolean} isSuccess Whether the new result is a success or an error. + * @param {*} res The result. + * @private + */ +goog.async.Deferred.prototype.updateResult_ = function(isSuccess, res) { + this.fired_ = true; + this.result_ = res; + this.hadError_ = !isSuccess; + this.fire_(); +}; + + +/** + * Verifies that the Deferred has not yet been fired. + * + * @private + * @throws {Error} If this has already been fired. + */ +goog.async.Deferred.prototype.check_ = function() { + if (this.hasFired()) { + if (!this.silentlyCanceled_) { + throw new goog.async.Deferred.AlreadyCalledError(this); + } + this.silentlyCanceled_ = false; + } +}; + + +/** + * Fire the execution sequence for this Deferred by passing the starting result + * to the first registered callback. + * @param {VALUE=} opt_result The starting result. + */ +goog.async.Deferred.prototype.callback = function(opt_result) { + this.check_(); + this.assertNotDeferred_(opt_result); + this.updateResult_(true /* isSuccess */, opt_result); +}; + + +/** + * Fire the execution sequence for this Deferred by passing the starting error + * result to the first registered errback. + * @param {*=} opt_result The starting error. + */ +goog.async.Deferred.prototype.errback = function(opt_result) { + this.check_(); + this.assertNotDeferred_(opt_result); + this.makeStackTraceLong_(opt_result); + this.updateResult_(false /* isSuccess */, opt_result); +}; + + +/** + * Attempt to make the error's stack trace be long in that it contains the + * stack trace from the point where the deferred was created on top of the + * current stack trace to give additional context. + * @param {*} error + * @private + */ +goog.async.Deferred.prototype.makeStackTraceLong_ = function(error) { + if (!goog.async.Deferred.LONG_STACK_TRACES) { + return; + } + if (this.constructorStack_ && goog.isObject(error) && error.stack && + // Stack looks like it was system generated. See + // https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi + (/^[^\n]+(\n [^\n]+)+/).test(error.stack)) { + error.stack = error.stack + '\nDEFERRED OPERATION:\n' + + this.constructorStack_; + } +}; + + +/** + * Asserts that an object is not a Deferred. + * @param {*} obj The object to test. + * @throws {Error} Throws an exception if the object is a Deferred. + * @private + */ +goog.async.Deferred.prototype.assertNotDeferred_ = function(obj) { + goog.asserts.assert( + !(obj instanceof goog.async.Deferred), + 'An execution sequence may not be initiated with a blocking Deferred.'); +}; + + +/** + * Register a callback function to be called with a successful result. If no + * value is returned by the callback function, the result value is unchanged. If + * a new value is returned, it becomes the Deferred result and will be passed to + * the next callback in the execution sequence. + * + * If the function throws an error, the error becomes the new result and will be + * passed to the next errback in the execution chain. + * + * If the function returns a Deferred, the execution sequence will be blocked + * until that Deferred fires. Its result will be passed to the next callback (or + * errback if it is an error result) in this Deferred's execution sequence. + * + * @param {!function(this:T,VALUE):?} cb The function to be called with a + * successful result. + * @param {T=} opt_scope An optional scope to call the callback in. + * @return {!goog.async.Deferred} This Deferred. + * @template T + */ +goog.async.Deferred.prototype.addCallback = function(cb, opt_scope) { + return this.addCallbacks(cb, null, opt_scope); +}; + + +/** + * Register a callback function to be called with an error result. If no value + * is returned by the function, the error result is unchanged. If a new error + * value is returned or thrown, that error becomes the Deferred result and will + * be passed to the next errback in the execution sequence. + * + * If the errback function handles the error by returning a non-error value, + * that result will be passed to the next normal callback in the sequence. + * + * If the function returns a Deferred, the execution sequence will be blocked + * until that Deferred fires. Its result will be passed to the next callback (or + * errback if it is an error result) in this Deferred's execution sequence. + * + * @param {!function(this:T,?):?} eb The function to be called on an + * unsuccessful result. + * @param {T=} opt_scope An optional scope to call the errback in. + * @return {!goog.async.Deferred.<VALUE>} This Deferred. + * @template T + */ +goog.async.Deferred.prototype.addErrback = function(eb, opt_scope) { + return this.addCallbacks(null, eb, opt_scope); +}; + + +/** + * Registers one function as both a callback and errback. + * + * @param {!function(this:T,?):?} f The function to be called on any result. + * @param {T=} opt_scope An optional scope to call the function in. + * @return {!goog.async.Deferred} This Deferred. + * @template T + */ +goog.async.Deferred.prototype.addBoth = function(f, opt_scope) { + return this.addCallbacks(f, f, opt_scope); +}; + + +/** + * Registers a callback function and an errback function at the same position + * in the execution sequence. Only one of these functions will execute, + * depending on the error state during the execution sequence. + * + * NOTE: This is not equivalent to {@code def.addCallback().addErrback()}! If + * the callback is invoked, the errback will be skipped, and vice versa. + * + * @param {(function(this:T,VALUE):?)|null} cb The function to be called on a + * successful result. + * @param {(function(this:T,?):?)|null} eb The function to be called on an + * unsuccessful result. + * @param {T=} opt_scope An optional scope to call the functions in. + * @return {!goog.async.Deferred} This Deferred. + * @template T + */ +goog.async.Deferred.prototype.addCallbacks = function(cb, eb, opt_scope) { + goog.asserts.assert(!this.blocking_, 'Blocking Deferreds can not be re-used'); + this.sequence_.push([cb, eb, opt_scope]); + if (this.hasFired()) { + this.fire_(); + } + return this; +}; + + +/** + * Implements {@see goog.Thenable} for seamless integration with + * {@see goog.Promise}. + * Deferred results are mutable and may represent multiple values over + * their lifetime. Calling {@code then} on a Deferred returns a Promise + * with the result of the Deferred at that point in its callback chain. + * Note that if the Deferred result is never mutated, and only + * {@code then} calls are made, the Deferred will behave like a Promise. + * + * @override + */ +goog.async.Deferred.prototype.then = function(opt_onFulfilled, opt_onRejected, + opt_context) { + var resolve, reject; + var promise = new goog.Promise(function(res, rej) { + // Copying resolvers to outer scope, so that they are available when the + // deferred callback fires (which may be synchronous). + resolve = res; + reject = rej; + }); + this.addCallbacks(resolve, function(reason) { + if (reason instanceof goog.async.Deferred.CanceledError) { + promise.cancel(); + } else { + reject(reason); + } + }); + return promise.then(opt_onFulfilled, opt_onRejected, opt_context); +}; +goog.Thenable.addImplementation(goog.async.Deferred); + + +/** + * Links another Deferred to the end of this Deferred's execution sequence. The + * result of this execution sequence will be passed as the starting result for + * the chained Deferred, invoking either its first callback or errback. + * + * @param {!goog.async.Deferred} otherDeferred The Deferred to chain. + * @return {!goog.async.Deferred} This Deferred. + */ +goog.async.Deferred.prototype.chainDeferred = function(otherDeferred) { + this.addCallbacks( + otherDeferred.callback, otherDeferred.errback, otherDeferred); + return this; +}; + + +/** + * Makes this Deferred wait for another Deferred's execution sequence to + * complete before continuing. + * + * This is equivalent to adding a callback that returns {@code otherDeferred}, + * but doesn't prevent additional callbacks from being added to + * {@code otherDeferred}. + * + * @param {!goog.async.Deferred|!goog.Thenable} otherDeferred The Deferred + * to wait for. + * @return {!goog.async.Deferred} This Deferred. + */ +goog.async.Deferred.prototype.awaitDeferred = function(otherDeferred) { + if (!(otherDeferred instanceof goog.async.Deferred)) { + // The Thenable case. + return this.addCallback(function() { + return otherDeferred; + }); + } + return this.addCallback(goog.bind(otherDeferred.branch, otherDeferred)); +}; + + +/** + * Creates a branch off this Deferred's execution sequence, and returns it as a + * new Deferred. The branched Deferred's starting result will be shared with the + * parent at the point of the branch, even if further callbacks are added to the + * parent. + * + * All branches at the same stage in the execution sequence will receive the + * same starting value. + * + * @param {boolean=} opt_propagateCancel If cancel() is called on every child + * branch created with opt_propagateCancel, the parent will be canceled as + * well. + * @return {!goog.async.Deferred.<VALUE>} A Deferred that will be started with + * the computed result from this stage in the execution sequence. + */ +goog.async.Deferred.prototype.branch = function(opt_propagateCancel) { + var d = new goog.async.Deferred(); + this.chainDeferred(d); + if (opt_propagateCancel) { + d.parent_ = this; + this.branches_++; + } + return d; +}; + + +/** + * @return {boolean} Whether the execution sequence has been started on this + * Deferred by invoking {@code callback} or {@code errback}. + */ +goog.async.Deferred.prototype.hasFired = function() { + return this.fired_; +}; + + +/** + * @param {*} res The latest result in the execution sequence. + * @return {boolean} Whether the current result is an error that should cause + * the next errback to fire. May be overridden by subclasses to handle + * special error types. + * @protected + */ +goog.async.Deferred.prototype.isError = function(res) { + return res instanceof Error; +}; + + +/** + * @return {boolean} Whether an errback exists in the remaining sequence. + * @private + */ +goog.async.Deferred.prototype.hasErrback_ = function() { + return goog.array.some(this.sequence_, function(sequenceRow) { + // The errback is the second element in the array. + return goog.isFunction(sequenceRow[1]); + }); +}; + + +/** + * Exhausts the execution sequence while a result is available. The result may + * be modified by callbacks or errbacks, and execution will block if the + * returned result is an incomplete Deferred. + * + * @private + */ +goog.async.Deferred.prototype.fire_ = function() { + if (this.unhandledErrorId_ && this.hasFired() && this.hasErrback_()) { + // It is possible to add errbacks after the Deferred has fired. If a new + // errback is added immediately after the Deferred encountered an unhandled + // error, but before that error is rethrown, the error is unscheduled. + goog.async.Deferred.unscheduleError_(this.unhandledErrorId_); + this.unhandledErrorId_ = 0; + } + + if (this.parent_) { + this.parent_.branches_--; + delete this.parent_; + } + + var res = this.result_; + var unhandledException = false; + var isNewlyBlocked = false; + + while (this.sequence_.length && !this.blocked_) { + var sequenceEntry = this.sequence_.shift(); + + var callback = sequenceEntry[0]; + var errback = sequenceEntry[1]; + var scope = sequenceEntry[2]; + + var f = this.hadError_ ? errback : callback; + if (f) { + /** @preserveTry */ + try { + var ret = f.call(scope || this.defaultScope_, res); + + // If no result, then use previous result. + if (goog.isDef(ret)) { + // Bubble up the error as long as the return value hasn't changed. + this.hadError_ = this.hadError_ && (ret == res || this.isError(ret)); + this.result_ = res = ret; + } + + if (goog.Thenable.isImplementedBy(res)) { + isNewlyBlocked = true; + this.blocked_ = true; + } + + } catch (ex) { + res = ex; + this.hadError_ = true; + this.makeStackTraceLong_(res); + + if (!this.hasErrback_()) { + // If an error is thrown with no additional errbacks in the queue, + // prepare to rethrow the error. + unhandledException = true; + } + } + } + } + + this.result_ = res; + + if (isNewlyBlocked) { + var onCallback = goog.bind(this.continue_, this, true /* isSuccess */); + var onErrback = goog.bind(this.continue_, this, false /* isSuccess */); + + if (res instanceof goog.async.Deferred) { + res.addCallbacks(onCallback, onErrback); + res.blocking_ = true; + } else { + res.then(onCallback, onErrback); + } + } else if (goog.async.Deferred.STRICT_ERRORS && this.isError(res) && + !(res instanceof goog.async.Deferred.CanceledError)) { + this.hadError_ = true; + unhandledException = true; + } + + if (unhandledException) { + // Rethrow the unhandled error after a timeout. Execution will continue, but + // the error will be seen by global handlers and the user. The throw will + // be canceled if another errback is appended before the timeout executes. + // The error's original stack trace is preserved where available. + this.unhandledErrorId_ = goog.async.Deferred.scheduleError_(res); + } +}; + + +/** + * Creates a Deferred that has an initial result. + * + * @param {*=} opt_result The result. + * @return {!goog.async.Deferred} The new Deferred. + */ +goog.async.Deferred.succeed = function(opt_result) { + var d = new goog.async.Deferred(); + d.callback(opt_result); + return d; +}; + + +/** + * Creates a Deferred that fires when the given promise resolves. + * Use only during migration to Promises. + * + * @param {!goog.Promise.<T>} promise + * @return {!goog.async.Deferred.<T>} The new Deferred. + * @template T + */ +goog.async.Deferred.fromPromise = function(promise) { + var d = new goog.async.Deferred(); + d.callback(); + d.addCallback(function() { + return promise; + }); + return d; +}; + + +/** + * Creates a Deferred that has an initial error result. + * + * @param {*} res The error result. + * @return {!goog.async.Deferred} The new Deferred. + */ +goog.async.Deferred.fail = function(res) { + var d = new goog.async.Deferred(); + d.errback(res); + return d; +}; + + +/** + * Creates a Deferred that has already been canceled. + * + * @return {!goog.async.Deferred} The new Deferred. + */ +goog.async.Deferred.canceled = function() { + var d = new goog.async.Deferred(); + d.cancel(); + return d; +}; + + +/** + * Normalizes values that may or may not be Deferreds. + * + * If the input value is a Deferred, the Deferred is branched (so the original + * execution sequence is not modified) and the input callback added to the new + * branch. The branch is returned to the caller. + * + * If the input value is not a Deferred, the callback will be executed + * immediately and an already firing Deferred will be returned to the caller. + * + * In the following (contrived) example, if <code>isImmediate</code> is true + * then 3 is alerted immediately, otherwise 6 is alerted after a 2-second delay. + * + * <pre> + * var value; + * if (isImmediate) { + * value = 3; + * } else { + * value = new goog.async.Deferred(); + * setTimeout(function() { value.callback(6); }, 2000); + * } + * + * var d = goog.async.Deferred.when(value, alert); + * </pre> + * + * @param {*} value Deferred or normal value to pass to the callback. + * @param {!function(this:T, ?):?} callback The callback to execute. + * @param {T=} opt_scope An optional scope to call the callback in. + * @return {!goog.async.Deferred} A new Deferred that will call the input + * callback with the input value. + * @template T + */ +goog.async.Deferred.when = function(value, callback, opt_scope) { + if (value instanceof goog.async.Deferred) { + return value.branch(true).addCallback(callback, opt_scope); + } else { + return goog.async.Deferred.succeed(value).addCallback(callback, opt_scope); + } +}; + + + +/** + * An error sub class that is used when a Deferred has already been called. + * @param {!goog.async.Deferred} deferred The Deferred. + * + * @constructor + * @extends {goog.debug.Error} + */ +goog.async.Deferred.AlreadyCalledError = function(deferred) { + goog.debug.Error.call(this); + + /** + * The Deferred that raised this error. + * @type {goog.async.Deferred} + */ + this.deferred = deferred; +}; +goog.inherits(goog.async.Deferred.AlreadyCalledError, goog.debug.Error); + + +/** @override */ +goog.async.Deferred.AlreadyCalledError.prototype.message = + 'Deferred has already fired'; + + +/** @override */ +goog.async.Deferred.AlreadyCalledError.prototype.name = 'AlreadyCalledError'; + + + +/** + * An error sub class that is used when a Deferred is canceled. + * + * @param {!goog.async.Deferred} deferred The Deferred object. + * @constructor + * @extends {goog.debug.Error} + */ +goog.async.Deferred.CanceledError = function(deferred) { + goog.debug.Error.call(this); + + /** + * The Deferred that raised this error. + * @type {goog.async.Deferred} + */ + this.deferred = deferred; +}; +goog.inherits(goog.async.Deferred.CanceledError, goog.debug.Error); + + +/** @override */ +goog.async.Deferred.CanceledError.prototype.message = 'Deferred was canceled'; + + +/** @override */ +goog.async.Deferred.CanceledError.prototype.name = 'CanceledError'; + + + +/** + * Wrapper around errors that are scheduled to be thrown by failing deferreds + * after a timeout. + * + * @param {*} error Error from a failing deferred. + * @constructor + * @final + * @private + * @struct + */ +goog.async.Deferred.Error_ = function(error) { + /** @const @private {number} */ + this.id_ = goog.global.setTimeout(goog.bind(this.throwError, this), 0); + + /** @const @private {*} */ + this.error_ = error; +}; + + +/** + * Actually throws the error and removes it from the list of pending + * deferred errors. + */ +goog.async.Deferred.Error_.prototype.throwError = function() { + goog.asserts.assert(goog.async.Deferred.errorMap_[this.id_], + 'Cannot throw an error that is not scheduled.'); + delete goog.async.Deferred.errorMap_[this.id_]; + throw this.error_; +}; + + +/** + * Resets the error throw timer. + */ +goog.async.Deferred.Error_.prototype.resetTimer = function() { + goog.global.clearTimeout(this.id_); +}; + + +/** + * Map of unhandled errors scheduled to be rethrown in a future timestep. + * @private {!Object.<number|string, goog.async.Deferred.Error_>} + */ +goog.async.Deferred.errorMap_ = {}; + + +/** + * Schedules an error to be thrown after a delay. + * @param {*} error Error from a failing deferred. + * @return {number} Id of the error. + * @private + */ +goog.async.Deferred.scheduleError_ = function(error) { + var deferredError = new goog.async.Deferred.Error_(error); + goog.async.Deferred.errorMap_[deferredError.id_] = deferredError; + return deferredError.id_; +}; + + +/** + * Unschedules an error from being thrown. + * @param {number} id Id of the deferred error to unschedule. + * @private + */ +goog.async.Deferred.unscheduleError_ = function(id) { + var error = goog.async.Deferred.errorMap_[id]; + if (error) { + error.resetTimer(); + delete goog.async.Deferred.errorMap_[id]; + } +}; + + +/** + * Asserts that there are no pending deferred errors. If there are any + * scheduled errors, one will be thrown immediately to make this function fail. + */ +goog.async.Deferred.assertNoErrors = function() { + var map = goog.async.Deferred.errorMap_; + for (var key in map) { + var error = map[key]; + error.resetTimer(); + error.throwError(); + } +}; diff --git a/third_party/google_input_tools/update.py b/third_party/google_input_tools/update.py new file mode 100755 index 0000000..ce0e79f --- /dev/null +++ b/third_party/google_input_tools/update.py @@ -0,0 +1,243 @@ +#!/usr/bin/python +# Copyright (c) 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. + +import logging +import optparse +import os +import re +import shutil +import sys + +_BASE_REGEX_STRING = '^\s*goog\.%s\(\s*[\'"](.+)[\'"]\s*\)' +require_regex = re.compile(_BASE_REGEX_STRING % 'require') +provide_regex = re.compile(_BASE_REGEX_STRING % 'provide') + +# Entry-points required to build a virtual keyboard. +namespaces = [ + 'i18n.input.chrome.inputview.Controller', + 'i18n.input.chrome.inputview.content.compact.letter', + 'i18n.input.chrome.inputview.content.compact.util', + 'i18n.input.chrome.inputview.content.compact.symbol', + 'i18n.input.chrome.inputview.content.compact.more', + 'i18n.input.chrome.inputview.content.compact.numberpad', + 'i18n.input.chrome.inputview.content.ContextlayoutUtil', + 'i18n.input.chrome.inputview.content.util', + 'i18n.input.chrome.inputview.EmojiType', + 'i18n.input.chrome.inputview.layouts.CompactSpaceRow', + 'i18n.input.chrome.inputview.layouts.RowsOf101', + 'i18n.input.chrome.inputview.layouts.RowsOf102', + 'i18n.input.chrome.inputview.layouts.RowsOfCompact', + 'i18n.input.chrome.inputview.layouts.RowsOfJP', + 'i18n.input.chrome.inputview.layouts.SpaceRow', + 'i18n.input.chrome.inputview.layouts.util', + 'i18n.input.hwt.util'] + + +def ProcessFile(filename): + """Extracts provided and required namespaces. + + Description: + Scans Javascript file for provied and required namespaces. + + Args: + filename: name of the file to process. + + Returns: + Pair of lists, where the first list contains namespaces provided by the file + and the second contains a list of requirements. + """ + provides = [] + requires = [] + file_handle = open(filename, 'r') + try: + for line in file_handle: + if re.match(require_regex, line): + requires.append(re.search(require_regex, line).group(1)) + if re.match(provide_regex, line): + provides.append(re.search(provide_regex, line).group(1)) + finally: + file_handle.close() + return provides, requires + + +def ExpandDirectories(refs): + """Expands any directory references into inputs. + + Description: + Looks for any directories in the provided references. Found directories + are recursively searched for .js files. + + Args: + refs: a list of directories. + + Returns: + Pair of maps, where the first maps each namepace to the filename that + provides the namespace, and the second maps a filename to prerequisite + namespaces. + """ + providers = {} + requirements = {} + for ref in refs: + if os.path.isdir(ref): + for (root, dirs, files) in os.walk(ref): + for name in files: + if name.endswith('js'): + filename = os.path.join(root, name) + provides, requires = ProcessFile(filename) + for p in provides: + providers[p] = filename + requirements[filename] = [] + for r in requires: + requirements[filename].append(r) + return providers, requirements + + +def ExtractDependencies(namespace, providers, requirements, dependencies): + """Recursively extracts all dependencies for a namespace. + Description: + Recursively extracts all dependencies for a namespace. + + Args: + namespace: The namespace to process. + providers: Mapping of namespace to filename that provides the namespace. + requireemnts: Mapping of filename to a list of prerequisite namespaces. + Returns: + """ + if namespace in providers: + filename = providers[namespace] + if not filename in dependencies: + for ns in requirements[filename]: + ExtractDependencies(ns, providers, requirements, dependencies) + dependencies.add(filename) + + +def HomeDir(): + """Resolves the user's home directory.""" + + return os.path.expanduser('~') + + +def ExpandPathRelativeToHome(path): + """Resolves a path that is relative to the home directory. + + Args: + path: Relative path. + + Returns: + Resolved path. + """ + + return os.path.join(os.path.expanduser('~'), path) + + +def GetGoogleInputToolsSandboxFromOptions(options): + """Generate the input-input-tools path from the --input flag. + + Args: + options: Flags to update.py. + Returns: + Path to the google-input-tools sandbox. + """ + + path = options.input + if not path: + path = ExpandPathRelativeToHome('google-input-tools') + print 'Unspecified path for google-input-tools. Defaulting to %s' % path + return path + + +def GetClosureLibrarySandboxFromOptions(options): + """Generate the closure-library path from the --input flag. + + Args: + options: Flags to update.py. + Returns: + Path to the closure-library sandbox. + """ + + path = options.lib + if not path: + path = ExpandPathRelativeToHome('closure-library') + print 'Unspecified path for closure-library. Defaulting to %s' % path + return path + + +def CopyFile(source, target): + """Copies a file from the source to the target location. + + Args: + source: Path to the source file to copy. + target: Path to the target location to copy the file. + """ + print '%s --> %s' % (source, target) + if not os.path.exists(os.path.dirname(target)): + os.makedirs(os.path.dirname(target)) + shutil.copy(source, target) + + +def UpdateFile(filename, input_source, closure_source): + """Updates files in third_party/google_input_tools. + Args: + filename: The file to update. + input_source: Root of the google_input_tools sandbox. + closure_source: Root of the closure_library sandbox. + """ + target = '' + if filename.startswith(input_source): + target = os.path.join('src', filename[len(input_source)+1:]) + elif filename.startswith(closure_source): + target = os.path.join('third_party/closure_library', \ + filename[len(closure_source)+1:]) + if len(target) > 0: + CopyFile(filename, target) + + +def main(): + """The entrypoint for this script.""" + + logging.basicConfig(format='update.py: %(message)s', level=logging.INFO) + + usage = 'usage: %prog [options] arg' + parser = optparse.OptionParser(usage) + parser.add_option('-i', + '--input', + dest='input', + action='append', + help='Path to the google-input-tools sandbox.') + parser.add_option('-l', + '--lib', + dest='lib', + action='store', + help='Path to the closure-library sandbox.') + + (options, args) = parser.parse_args() + + input_path = GetGoogleInputToolsSandboxFromOptions(options) + closure_library_path = GetClosureLibrarySandboxFromOptions(options) + + print 'iput_path = %s' % input_path + print 'closure_library_path = %s' % closure_library_path + + if not os.path.isdir(input_path): + print 'Could not find google-input-tools sandbox.' + exit(1) + if not os.path.isdir(closure_library_path): + print 'Could not find closure-library sandbox.' + exit(1) + + (providers, requirements) = \ + ExpandDirectories([os.path.join(input_path, 'chrome'), + closure_library_path]) + + dependencies = set() + + for name in namespaces: + ExtractDependencies(name, providers, requirements, dependencies) + + for name in dependencies: + UpdateFile(name, input_path, closure_library_path) + +if __name__ == '__main__': + main() |