123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- // 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 Input Method Editors (IMEs) are OS-level widgets that make
- * it easier to type non-ascii characters on ascii keyboards (in particular,
- * characters that require more than one keystroke).
- *
- * When the user wants to type such a character, a modal menu pops up and
- * suggests possible "next" characters in the IME character sequence. After
- * typing N characters, the user hits "enter" to commit the IME to the field.
- * N differs from language to language.
- *
- * This class offers high-level events for how the user is interacting with the
- * IME in editable regions.
- *
- * Known Issues:
- *
- * Firefox always fires an extra pair of compositionstart/compositionend events.
- * We do not normalize for this.
- *
- * Opera does not fire any IME events.
- *
- * Spurious UPDATE events are common on all browsers.
- *
- * We currently do a bad job detecting when the IME closes on IE, and
- * make a "best effort" guess on when we know it's closed.
- *
- * @author nicksantos@google.com (Nick Santos) (Ported to Closure)
- */
- goog.provide('goog.events.ImeHandler');
- goog.provide('goog.events.ImeHandler.Event');
- goog.provide('goog.events.ImeHandler.EventType');
- goog.require('goog.events.Event');
- goog.require('goog.events.EventHandler');
- goog.require('goog.events.EventTarget');
- goog.require('goog.events.EventType');
- goog.require('goog.events.KeyCodes');
- goog.require('goog.userAgent');
- /**
- * Dispatches high-level events for IMEs.
- * @param {Element} el The element to listen on.
- * @extends {goog.events.EventTarget}
- * @constructor
- * @final
- */
- goog.events.ImeHandler = function(el) {
- goog.events.ImeHandler.base(this, 'constructor');
- /**
- * The element to listen on.
- * @type {Element}
- * @private
- */
- this.el_ = el;
- /**
- * Tracks the keyup event only, because it has a different life-cycle from
- * other events.
- * @type {goog.events.EventHandler<!goog.events.ImeHandler>}
- * @private
- */
- this.keyUpHandler_ = new goog.events.EventHandler(this);
- /**
- * Tracks all the browser events.
- * @type {goog.events.EventHandler<!goog.events.ImeHandler>}
- * @private
- */
- this.handler_ = new goog.events.EventHandler(this);
- if (goog.events.ImeHandler.USES_COMPOSITION_EVENTS) {
- this.handler_
- .listen(
- el, goog.events.EventType.COMPOSITIONSTART,
- this.handleCompositionStart_)
- .listen(
- el, goog.events.EventType.COMPOSITIONEND,
- this.handleCompositionEnd_)
- .listen(
- el, goog.events.EventType.COMPOSITIONUPDATE,
- this.handleTextModifyingInput_);
- }
- this.handler_
- .listen(el, goog.events.EventType.TEXTINPUT, this.handleTextInput_)
- .listen(el, goog.events.EventType.TEXT, this.handleTextModifyingInput_)
- .listen(el, goog.events.EventType.KEYDOWN, this.handleKeyDown_);
- };
- goog.inherits(goog.events.ImeHandler, goog.events.EventTarget);
- /**
- * Event types fired by ImeHandler. These events do not make any guarantees
- * about whether they were fired before or after the event in question.
- * @enum {string}
- */
- goog.events.ImeHandler.EventType = {
- // After the IME opens.
- START: 'startIme',
- // An update to the state of the IME. An 'update' does not necessarily mean
- // that the text contents of the field were modified in any way.
- UPDATE: 'updateIme',
- // After the IME closes.
- END: 'endIme'
- };
- /**
- * An event fired by ImeHandler.
- * @param {goog.events.ImeHandler.EventType} type The type.
- * @param {goog.events.BrowserEvent} reason The trigger for this event.
- * @constructor
- * @extends {goog.events.Event}
- * @final
- */
- goog.events.ImeHandler.Event = function(type, reason) {
- goog.events.ImeHandler.Event.base(this, 'constructor', type);
- /**
- * The event that triggered this.
- * @type {goog.events.BrowserEvent}
- */
- this.reason = reason;
- };
- goog.inherits(goog.events.ImeHandler.Event, goog.events.Event);
- /**
- * Whether to use the composition events.
- * @type {boolean}
- */
- goog.events.ImeHandler.USES_COMPOSITION_EVENTS = goog.userAgent.GECKO ||
- (goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher(532));
- /**
- * Stores whether IME mode is active.
- * @type {boolean}
- * @private
- */
- goog.events.ImeHandler.prototype.imeMode_ = false;
- /**
- * The keyCode value of the last keyDown event. This value is used for
- * identiying whether or not a textInput event is sent by an IME.
- * @type {number}
- * @private
- */
- goog.events.ImeHandler.prototype.lastKeyCode_ = 0;
- /**
- * @return {boolean} Whether an IME is active.
- */
- goog.events.ImeHandler.prototype.isImeMode = function() {
- return this.imeMode_;
- };
- /**
- * Handles the compositionstart event.
- * @param {goog.events.BrowserEvent} e The event.
- * @private
- */
- goog.events.ImeHandler.prototype.handleCompositionStart_ = function(e) {
- this.handleImeActivate_(e);
- };
- /**
- * Handles the compositionend event.
- * @param {goog.events.BrowserEvent} e The event.
- * @private
- */
- goog.events.ImeHandler.prototype.handleCompositionEnd_ = function(e) {
- this.handleImeDeactivate_(e);
- };
- /**
- * Handles the compositionupdate and text events.
- * @param {goog.events.BrowserEvent} e The event.
- * @private
- */
- goog.events.ImeHandler.prototype.handleTextModifyingInput_ = function(e) {
- if (this.isImeMode()) {
- this.processImeComposition_(e);
- }
- };
- /**
- * Handles IME activation.
- * @param {goog.events.BrowserEvent} e The event.
- * @private
- */
- goog.events.ImeHandler.prototype.handleImeActivate_ = function(e) {
- if (this.imeMode_) {
- return;
- }
- // Listens for keyup events to handle unexpected IME keydown events on older
- // versions of webkit.
- //
- // In those versions, we currently use textInput events deactivate IME
- // (see handleTextInput_() for the reason). However,
- // Safari fires a keydown event (as a result of pressing keys to commit IME
- // text) with keyCode == WIN_IME after textInput event. This activates IME
- // mode again unnecessarily. To prevent this problem, listens keyup events
- // which can use to determine whether IME text has been committed.
- if (goog.userAgent.WEBKIT &&
- !goog.events.ImeHandler.USES_COMPOSITION_EVENTS) {
- this.keyUpHandler_.listen(
- this.el_, goog.events.EventType.KEYUP, this.handleKeyUpSafari4_);
- }
- this.imeMode_ = true;
- this.dispatchEvent(
- new goog.events.ImeHandler.Event(
- goog.events.ImeHandler.EventType.START, e));
- };
- /**
- * Handles the IME compose changes.
- * @param {goog.events.BrowserEvent} e The event.
- * @private
- */
- goog.events.ImeHandler.prototype.processImeComposition_ = function(e) {
- this.dispatchEvent(
- new goog.events.ImeHandler.Event(
- goog.events.ImeHandler.EventType.UPDATE, e));
- };
- /**
- * Handles IME deactivation.
- * @param {goog.events.BrowserEvent} e The event.
- * @private
- */
- goog.events.ImeHandler.prototype.handleImeDeactivate_ = function(e) {
- this.imeMode_ = false;
- this.keyUpHandler_.removeAll();
- this.dispatchEvent(
- new goog.events.ImeHandler.Event(
- goog.events.ImeHandler.EventType.END, e));
- };
- /**
- * Handles a key down event.
- * @param {!goog.events.BrowserEvent} e The event.
- * @private
- */
- goog.events.ImeHandler.prototype.handleKeyDown_ = function(e) {
- // Firefox and Chrome have a separate event for IME composition ('text'
- // and 'compositionupdate', respectively), other browsers do not.
- if (!goog.events.ImeHandler.USES_COMPOSITION_EVENTS) {
- var imeMode = this.isImeMode();
- // If we're in IE and we detect an IME input on keyDown then activate
- // the IME, otherwise if the imeMode was previously active, deactivate.
- if (!imeMode && e.keyCode == goog.events.KeyCodes.WIN_IME) {
- this.handleImeActivate_(e);
- } else if (imeMode && e.keyCode != goog.events.KeyCodes.WIN_IME) {
- if (goog.events.ImeHandler.isImeDeactivateKeyEvent_(e)) {
- this.handleImeDeactivate_(e);
- }
- } else if (imeMode) {
- this.processImeComposition_(e);
- }
- }
- // Safari on Mac doesn't send IME events in the right order so that we must
- // ignore some modifier key events to insert IME text correctly.
- if (goog.events.ImeHandler.isImeDeactivateKeyEvent_(e)) {
- this.lastKeyCode_ = e.keyCode;
- }
- };
- /**
- * Handles a textInput event.
- * @param {!goog.events.BrowserEvent} e The event.
- * @private
- */
- goog.events.ImeHandler.prototype.handleTextInput_ = function(e) {
- // Some WebKit-based browsers including Safari 4 don't send composition
- // events. So, we turn down IME mode when it's still there.
- if (!goog.events.ImeHandler.USES_COMPOSITION_EVENTS &&
- goog.userAgent.WEBKIT &&
- this.lastKeyCode_ == goog.events.KeyCodes.WIN_IME && this.isImeMode()) {
- this.handleImeDeactivate_(e);
- }
- };
- /**
- * Handles the key up event for any IME activity. This handler is just used to
- * prevent activating IME unnecessary in Safari at this time.
- * @param {!goog.events.BrowserEvent} e The event.
- * @private
- */
- goog.events.ImeHandler.prototype.handleKeyUpSafari4_ = function(e) {
- if (this.isImeMode()) {
- switch (e.keyCode) {
- // These keyup events indicates that IME text has been committed or
- // cancelled. We should turn off IME mode when these keyup events
- // received.
- case goog.events.KeyCodes.ENTER:
- case goog.events.KeyCodes.TAB:
- case goog.events.KeyCodes.ESC:
- this.handleImeDeactivate_(e);
- break;
- }
- }
- };
- /**
- * Returns whether the given event should be treated as an IME
- * deactivation trigger.
- * @param {!goog.events.Event} e The event.
- * @return {boolean} Whether the given event is an IME deactivate trigger.
- * @private
- */
- goog.events.ImeHandler.isImeDeactivateKeyEvent_ = function(e) {
- // Which key events involve IME deactivation depends on the user's
- // environment (i.e. browsers, platforms, and IMEs). Usually Shift key
- // and Ctrl key does not involve IME deactivation, so we currently assume
- // that these keys are not IME deactivation trigger.
- switch (e.keyCode) {
- case goog.events.KeyCodes.SHIFT:
- case goog.events.KeyCodes.CTRL:
- return false;
- default:
- return true;
- }
- };
- /** @override */
- goog.events.ImeHandler.prototype.disposeInternal = function() {
- this.handler_.dispose();
- this.keyUpHandler_.dispose();
- this.el_ = null;
- goog.events.ImeHandler.base(this, 'disposeInternal');
- };
|