/* * Copyright 2009, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * @fileoverview This file contains various event related functions for * o3d. It puts them in the 'event' module on the o3djs object. * * TODO Add selenium tests. * * */ o3djs.provide('o3djs.event'); /** * A Module for handling events related to o3d and various browsers. * @namespace */ o3djs.event = o3djs.event || {}; /** * @param {string} inStr base string. * @param {string} extraStr string to append. * @return {string} inStr + ' ' + extraStr, or just inStr if extraStr is ''. */ o3djs.event.appendWithSpace = function(inStr, extraStr) { return (inStr.length == 0) ? extraStr : inStr + ' ' + extraStr; }; /** * @param {boolean} state whether to append or not. * @param {string} inStr base string. * @param {string} extraStr string to append. * @return {string} inStr + ' ' + extraStr, or just inStr if state is false. */ o3djs.event.appendWithSpaceIf = function(state, inStr, extraStr) { return (state) ? o3djs.event.appendWithSpace(inStr, extraStr) : inStr; }; /** * Builds a DOM-level 3 modifier string for a KeyboardEvent - see * http://www.w3.org/TR/DOM-Level-3-Events/events.html * #Events-KeyboardEvents-Interfaces. * @param {boolean} control whether the control key is down. * @param {boolean} alt whether the alt/option key is down. * @param {boolean} shift whether the shift key is down. * @param {boolean} meta whether the meta/command key is down. * @return {string} space delimited list of keys that are down. */ o3djs.event.getModifierString = function(control, alt, shift, meta) { var modStr = o3djs.event.appendWithSpaceIf(control, '', 'Control'); modStr = o3djs.event.appendWithSpaceIf(alt, modStr, 'Alt'); modStr = o3djs.event.appendWithSpaceIf(shift, modStr, 'Shift'); return o3djs.event.appendWithSpaceIf(meta, modStr, 'Meta'); }; /** * Pad a string with leading zeroes if needed until it is the length desired. * @param {string} str The input string, probably representing a number. * @param {number} to_length The desired minimum length of string with padding. * @return {string} A string padded with leading zeroes as needed to be the * length desired. */ o3djs.event.padWithLeadingZeroes = function(str, to_length) { while (str.length < to_length) str = '0' + str; return str; }; /** * Creates a keyIdentifer string for a given keystroke as specified in the w3c * spec on http://www.w3.org/TR/DOM-Level-3-Events/events.html. * @param {number} charCode numeric unicode code point as reported by the OS. * @param {number} keyCode numeric keyCode as reported by the OS, currently * unused but will probably be necessary in the future. * @return {string} eg 'Left' or 'U+0040'. */ o3djs.event.getKeyIdentifier = function(charCode, keyCode) { if (!charCode) { // TODO: This works for webkit for keydown and keyup, for basic // alphanumeric keys, at least. Likely it needs lots of work to handle // accented characters, various keyboards, etc., as does the rest of our // keyboard event code. charCode = keyCode; } switch (charCode) { case 3: case 13: return 'Enter'; // spec merges these. case 37: return 'Left'; case 39: return 'Right'; case 38: return 'Up'; case 40: return 'Down'; } charCode = (charCode >= 97 && charCode <= 122) ? charCode - 32 : charCode; var keyStr = charCode.toString(16).toUpperCase(); return 'U+' + o3djs.event.padWithLeadingZeroes(keyStr, 4); }; /** Takes a keyIdentifier string and remaps it to an ASCII/Unicode value * suitable for javascript event handling. * @param {string} keyIdent a keyIdentifier string as generated above. * @return {number} the numeric Unicode code point represented. */ o3djs.event.keyIdentifierToChar = function(keyIdent) { if (keyIdent && typeof(keyIdent) == 'string') { switch (keyIdent) { case 'Enter': return 13; case 'Left': return 37; case 'Right': return 39; case 'Up': return 38; case 'Down': return 40; } if (keyIdent.indexOf('U+') == 0) return parseInt(keyIdent.substr(2).toUpperCase(), 16); } return 0; }; /** * Extracts the key char in number form from the event, in a cross-browser * manner. * @param {!Event} event . * @return {number} unicode code point for the key. */ o3djs.event.getEventKeyChar = function(event) { if (!event) { event = window.event; } var charCode = 0; if (event.keyIdentifier) charCode = o3djs.event.keyIdentifierToChar(event.keyIdentifier); if (!charCode) charCode = (window.event) ? window.event.keyCode : event.charCode; if (!charCode) charCode = event.keyCode; return charCode; }; /** * Cancel an event we've handled so it stops propagating upwards. * The cancelBubble is for IE, stopPropagation is for all other browsers. * preventDefault ensures that the default action is also canceled. * @param {!Event} event - the event to cancel. */ o3djs.event.cancel = function(event) { if (!event) event = window.event; event.cancelBubble = true; if (event.stopPropagation) event.stopPropagation(); if (event.preventDefault) event.preventDefault(); }; /** * Convenience function to setup synthesizing and dispatching of keyboard events * whenever the focussed plug-in calls Javascript to report a keyboard action. * @param {!Element} pluginObject the where the o3d plugin lives, * which the caller probably obtained by calling getElementById. */ o3djs.event.startKeyboardEventSynthesis = function(pluginObject) { var handler = function(event) { o3djs.event.onKey(event, pluginObject); }; o3djs.event.addEventListener(pluginObject, 'keypress', handler); o3djs.event.addEventListener(pluginObject, 'keydown', handler); o3djs.event.addEventListener(pluginObject, 'keyup', handler); }; /** * Dispatches a DOM-level 3 KeyboardEvent when called back by the plugin. * see http://www.w3.org/TR/DOM-Level-3-Events/events.html * #Events-KeyboardEvents-Interfaces * see http://developer.mozilla.org/en/DOM/event.initKeyEvent * @param {!Event} event an O3D event object. * @param {!Element} pluginObject the plugin object on the page. */ o3djs.event.onKey = function(event, pluginObject) { var k_evt = o3djs.event.createKeyEvent(event.type, event.charCode, event.keyCode, event.ctrlKey, event.altKey, event.shiftKey, event.metaKey); if (k_evt) { if (pluginObject.parentNode.dispatchEvent) { // Using the pluginObject itself fails for non-capturing event listeners // on keypress events on Firefox only, as far as I've been able to // determine. I have no idea why. pluginObject.parentNode.dispatchEvent(k_evt); } else if (pluginObject.fireEvent) { pluginObject.fireEvent('on' + event.type, k_evt); } } }; /** * Creates a DOM-level 3 KeyboardEvent. * see http://www.w3.org/TR/DOM-Level-3-Events/events.html * #Events-KeyboardEvents-Interfaces. * see http://developer.mozilla.org/en/DOM/event.initKeyEvent * @param {string} eventName one of 'keypress', 'keydown' or 'keyup'. * @param {number} charCode the character code for the key. * @param {number} keyCode the key code for the key. * @param {boolean} control whether the control key is down. * @param {boolean} alt whether the alt/option key is down. * @param {boolean} shift whether the shift key is down. * @param {boolean} meta whether the meta/command key is down. */ o3djs.event.createKeyEvent = function(eventName, charCode, keyCode, control, alt, shift, meta) { var k_evt; var keyIdentifier = o3djs.event.getKeyIdentifier(charCode, keyCode); if (document.createEvent) { k_evt = document.createEvent('KeyboardEvent'); if (k_evt.initKeyboardEvent) { // WebKit. k_evt.initKeyboardEvent(eventName, true, true, window, keyIdentifier, 0, control, alt, shift, meta); // TODO: These actually fail to do anything in Chrome; those are // read-only fields, and it's not setting them in initKeyboardEvent. k_evt.charCode = charCode; if (eventName == 'keypress') k_evt.keyCode = charCode; else k_evt.keyCode = keyCode; } else if (k_evt.initKeyEvent) { // FF. k_evt.initKeyEvent(eventName, true, true, window, control, alt, shift, meta, keyCode, charCode); k_evt.keyIdentifier = keyIdentifier; } } else if (document.createEventObject) { k_evt = document.createEventObject(); k_evt.ctrlKey = control; k_evt.altKey = alt; k_evt.shiftKey = shift; k_evt.metaKey = meta; k_evt.keyCode = charCode; // Emulate IE charcode-in-the-keycode onkeypress. k_evt.keyIdentifier = keyIdentifier; } k_evt.synthetic = true; return k_evt; }; /* * Function to create a closure that will call each event handler in an array * whenever it gets called, passing its single argument through to the * sub-handlers. The sub-handlers may either be functions or EventListeners. * This is generally expected to be used only through * o3djs.event.addEventListener. * @param {!Array.<*>} listenerSet an array of handlers. * @return {!function(*): void} a closure to be used to multiplex out * event-handling. */ o3djs.event.createEventHandler = function(listenerSet) { return function(event) { var length = listenerSet.length; for (var index = 0; index < length; ++index) { var handler = listenerSet[index]; if (typeof(handler.handleEvent) == 'function') { handler.handleEvent(event); } else { handler(event); } } } }; /** * Convenience function to manage event listeners on the o3d plugin object, * intended as a drop-in replacement for the DOM addEventListener [with slightly * different arguments, but the same effect]. * @param {!Element} pluginObject the html object where the o3d plugin lives, * which the caller probably obtained by calling getElementById or makeClients. * @param {string} type the event type on which to trigger, e.g. 'mousedown', * 'mousemove', etc. * @param {!Object} handler either a function or an EventListener object. */ o3djs.event.addEventListener = function(pluginObject, type, handler) { if (!handler || typeof(type) != 'string' || (typeof(handler) != 'function' && typeof(handler.handleEvent) != 'function')) { throw new Error('Invalid argument.'); } pluginObject.o3d_eventRegistry = pluginObject.o3d_eventRegistry || []; var registry = pluginObject.o3d_eventRegistry; var listenerSet = registry[type]; if (!listenerSet || listenerSet.length == 0) { listenerSet = registry[type] = []; pluginObject.client.setEventCallback(type, o3djs.event.createEventHandler(listenerSet)); } else { for (var index in listenerSet) { if (listenerSet[index] == handler) { return; // We're idempotent. } } } listenerSet.push(handler); }; /** * Convenience function to manage event listeners on the o3d plugin object, * intended as a drop-in replacement for the DOM removeEventListener [with * slightly different arguments, but the same effect]. * @param {!Element} pluginObject the where the o3d plugin lives, * which the caller probably obtained by calling getElementById. * @param {string} type the event type on which the handler to be removed was to * trigger, e.g. 'mousedown', 'mousemove', etc. * @param {!Object} handler either a function or an EventListener object. */ o3djs.event.removeEventListener = function(pluginObject, type, handler) { var registry = pluginObject.o3d_eventRegistry; if (!registry) { return; } var listenerSet = registry[type]; if (!listenerSet) { return; } for (var index in listenerSet) { if (listenerSet[index] == handler) { if (listenerSet.length == 1) { pluginObject.client.clearEventCallback(type); } listenerSet.splice(index, 1); break; } } };