text: /* ***** BEGIN LICENSE BLOCK *****
   * Distributed under the BSD license:
   *
   * Copyright (c) 2010, Ajax.org B.V.
   * 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 Ajax.org B.V. 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 AJAX.ORG B.V. 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.
   *
   * ***** END LICENSE BLOCK ***** */
  
  define(function (require, exports, module) {
      "use strict";
  
      var keys = require("./keys");
      var useragent = require("./useragent");
  
      var pressedKeys = null;
      var ts = 0;
  
      exports.addListener = function (elem, type, callback) {
          if (elem.addEventListener) {
              return elem.addEventListener(type, callback, false);
          }
          if (elem.attachEvent) {
              var wrapper = function () {
                  callback.call(elem, window.event);
              };
              callback._wrapper = wrapper;
              elem.attachEvent("on" + type, wrapper);
          }
      };
  
      exports.removeListener = function (elem, type, callback) {
          if (elem.removeEventListener) {
              return elem.removeEventListener(type, callback, false);
          }
          if (elem.detachEvent) {
              elem.detachEvent("on" + type, callback._wrapper || callback);
          }
      };
  
      /*
       * Prevents propagation and clobbers the default action of the passed event
       */
      exports.stopEvent = function (e) {
          exports.stopPropagation(e);
          exports.preventDefault(e);
          return false;
      };
  
      exports.stopPropagation = function (e) {
          if (e.stopPropagation)
              e.stopPropagation();
          else
              e.cancelBubble = true;
      };
  
      exports.preventDefault = function (e) {
          if (e.preventDefault)
              e.preventDefault();
          else
              e.returnValue = false;
      };
  
      /*
       * @return {Number} 0 for left button, 1 for middle button, 2 for right button
       */
      exports.getButton = function (e) {
          if (e.type == "dblclick")
              return 0;
          if (e.type == "contextmenu" || (useragent.isMac && (e.ctrlKey && !e.altKey && !e.shiftKey)))
              return 2;
  
          // DOM Event
          if (e.preventDefault) {
              return e.button;
          }
          // old IE
          else {
              return {1: 0, 2: 2, 4: 1}[e.button];
          }
      };
  
      exports.capture = function (el, eventHandler, releaseCaptureHandler) {
          function onMouseUp(e) {
              eventHandler && eventHandler(e);
              releaseCaptureHandler && releaseCaptureHandler(e);
  
              exports.removeListener(document, "mousemove", eventHandler, true);
              exports.removeListener(document, "mouseup", onMouseUp, true);
              exports.removeListener(document, "dragstart", onMouseUp, true);
          }
  
          exports.addListener(document, "mousemove", eventHandler, true);
          exports.addListener(document, "mouseup", onMouseUp, true);
          exports.addListener(document, "dragstart", onMouseUp, true);
  
          return onMouseUp;
      };
  
      exports.addTouchMoveListener = function (el, callback) {
          if ("ontouchmove" in el) {
              var startx, starty;
              exports.addListener(el, "touchstart", function (e) {
                  var touchObj = e.changedTouches[0];
                  startx = touchObj.clientX;
                  starty = touchObj.clientY;
              });
              exports.addListener(el, "touchmove", function (e) {
                  var factor = 1,
                      touchObj = e.changedTouches[0];
  
                  e.wheelX = -(touchObj.clientX - startx) / factor;
                  e.wheelY = -(touchObj.clientY - starty) / factor;
  
                  startx = touchObj.clientX;
                  starty = touchObj.clientY;
  
                  callback(e);
              });
          }
      };
  
      exports.addMouseWheelListener = function (el, callback) {
          if ("onmousewheel" in el) {
              exports.addListener(el, "mousewheel", function (e) {
                  var factor = 8;
                  if (e.wheelDeltaX !== undefined) {
                      e.wheelX = -e.wheelDeltaX / factor;
                      e.wheelY = -e.wheelDeltaY / factor;
                  } else {
                      e.wheelX = 0;
                      e.wheelY = -e.wheelDelta / factor;
                  }
                  callback(e);
              });
          } else if ("onwheel" in el) {
              exports.addListener(el, "wheel", function (e) {
                  var factor = 0.35;
                  switch (e.deltaMode) {
                      case e.DOM_DELTA_PIXEL:
                          e.wheelX = e.deltaX * factor || 0;
                          e.wheelY = e.deltaY * factor || 0;
                          break;
                      case e.DOM_DELTA_LINE:
                      case e.DOM_DELTA_PAGE:
                          e.wheelX = (e.deltaX || 0) * 5;
                          e.wheelY = (e.deltaY || 0) * 5;
                          break;
                  }
  
                  callback(e);
              });
          } else {
              exports.addListener(el, "DOMMouseScroll", function (e) {
                  if (e.axis && e.axis == e.HORIZONTAL_AXIS) {
                      e.wheelX = (e.detail || 0) * 5;
                      e.wheelY = 0;
                  } else {
                      e.wheelX = 0;
                      e.wheelY = (e.detail || 0) * 5;
                  }
                  callback(e);
              });
          }
      };
  
      exports.addMultiMouseDownListener = function (elements, timeouts, eventHandler, callbackName) {
          var clicks = 0;
          var startX, startY, timer;
          var eventNames = {
              2: "dblclick",
              3: "tripleclick",
              4: "quadclick"
          };
  
          function onMousedown(e) {
              if (exports.getButton(e) !== 0) {
                  clicks = 0;
              } else if (e.detail > 1) {
                  clicks++;
                  if (clicks > 4)
                      clicks = 1;
              } else {
                  clicks = 1;
              }
              if (useragent.isIE) {
                  var isNewClick = Math.abs(e.clientX - startX) > 5 || Math.abs(e.clientY - startY) > 5;
                  if (!timer || isNewClick)
                      clicks = 1;
                  if (timer)
                      clearTimeout(timer);
                  timer = setTimeout(function () {
                      timer = null
                  }, timeouts[clicks - 1] || 600);
  
                  if (clicks == 1) {
                      startX = e.clientX;
                      startY = e.clientY;
                  }
              }
  
              e._clicks = clicks;
  
              eventHandler[callbackName]("mousedown", e);
  
              if (clicks > 4)
                  clicks = 0;
              else if (clicks > 1)
                  return eventHandler[callbackName](eventNames[clicks], e);
          }
  
          function onDblclick(e) {
              clicks = 2;
              if (timer)
                  clearTimeout(timer);
              timer = setTimeout(function () {
                  timer = null
              }, timeouts[clicks - 1] || 600);
              eventHandler[callbackName]("mousedown", e);
              eventHandler[callbackName](eventNames[clicks], e);
          }
  
          if (!Array.isArray(elements))
              elements = [elements];
          elements.forEach(function (el) {
              exports.addListener(el, "mousedown", onMousedown);
              if (useragent.isOldIE)
                  exports.addListener(el, "dblclick", onDblclick);
          });
      };
  
      var getModifierHash = useragent.isMac && useragent.isOpera && !("KeyboardEvent" in window)
          ? function (e) {
              return 0 | (e.metaKey ? 1 : 0) | (e.altKey ? 2 : 0) | (e.shiftKey ? 4 : 0) | (e.ctrlKey ? 8 : 0);
          }
          : function (e) {
              return 0 | (e.ctrlKey ? 1 : 0) | (e.altKey ? 2 : 0) | (e.shiftKey ? 4 : 0) | (e.metaKey ? 8 : 0);
          };
  
      exports.getModifierString = function (e) {
          return keys.KEY_MODS[getModifierHash(e)];
      };
  
      function normalizeCommandKeys(callback, e, keyCode) {
          var hashId = getModifierHash(e);
  
          if (!useragent.isMac && pressedKeys) {
              if (e.getModifierState && (e.getModifierState("OS") || e.getModifierState("Win")))
                  hashId |= 8;
              if (pressedKeys.altGr) {
                  if ((3 & hashId) != 3)
                      pressedKeys.altGr = 0;
                  else
                      return;
              }
              if (keyCode === 18 || keyCode === 17) {
                  var location = "location" in e ? e.location : e.keyLocation;
                  if (keyCode === 17 && location === 1) {
                      if (pressedKeys[keyCode] == 1)
                          ts = e.timeStamp;
                  } else if (keyCode === 18 && hashId === 3 && location === 2) {
                      var dt = e.timeStamp - ts;
                      if (dt < 50)
                          pressedKeys.altGr = true;
                  }
              }
          }
  
          if (keyCode in keys.MODIFIER_KEYS) {
              keyCode = -1;
          }
  
          // keyCode of right command is 93 on mac and 92 on windows.
          // keyCode of left command key is 91
          if (hashId & 8 && (keyCode >= 91 && keyCode <= 93)) {
              keyCode = -1;
          }
  
          if (!hashId && keyCode === 13) {
              var location = "location" in e ? e.location : e.keyLocation;
              if (location === 3) {
                  callback(e, hashId, -keyCode);
                  if (e.defaultPrevented)
                      return;
              }
          }
  
          if (useragent.isChromeOS && hashId & 8) {
              callback(e, hashId, keyCode);
              if (e.defaultPrevented)
                  return;
              else
                  hashId &= ~8;
          }
  
          // If there is no hashId and the keyCode is not a function key, then
          // we don't call the callback as we don't handle a command key here
          // (it's a normal key/character input).
          if (!hashId && !(keyCode in keys.FUNCTION_KEYS) && !(keyCode in keys.PRINTABLE_KEYS)) {
              return false;
          }
  
          return callback(e, hashId, keyCode);
      }
  
  
      exports.addCommandKeyListener = function (el, callback) {
          var addListener = exports.addListener;
          if (useragent.isOldGecko || (useragent.isOpera && !("KeyboardEvent" in window))) {
              // Old versions of Gecko aka. Firefox < 4.0 didn't repeat the keydown
              // event if the user pressed the key for a longer time. Instead, the
              // keydown event was fired once and later on only the keypress event.
              // To emulate the 'right' keydown behavior, the keyCode of the initial
              // keyDown event is stored and in the following keypress events the
              // stores keyCode is used to emulate a keyDown event.
              var lastKeyDownKeyCode = null;
              addListener(el, "keydown", function (e) {
                  lastKeyDownKeyCode = e.keyCode;
              });
              addListener(el, "keypress", function (e) {
                  return normalizeCommandKeys(callback, e, lastKeyDownKeyCode);
              });
          } else {
              var lastDefaultPrevented = null;
  
              addListener(el, "keydown", function (e) {
                  pressedKeys[e.keyCode] = (pressedKeys[e.keyCode] || 0) + 1;
                  var result = normalizeCommandKeys(callback, e, e.keyCode);
                  lastDefaultPrevented = e.defaultPrevented;
                  return result;
              });
  
              addListener(el, "keypress", function (e) {
                  if (lastDefaultPrevented && (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey)) {
                      exports.stopEvent(e);
                      lastDefaultPrevented = null;
                  }
              });
  
              addListener(el, "keyup", function (e) {
                  pressedKeys[e.keyCode] = null;
              });
  
              if (!pressedKeys) {
                  resetPressedKeys();
                  addListener(window, "focus", resetPressedKeys);
              }
          }
      };
      function resetPressedKeys() {
          // console.log("resetting")
          pressedKeys = Object.create(null);
      }
  
      if (typeof window == "object" && window.postMessage && !useragent.isOldIE) {
          var postMessageId = 1;
          exports.nextTick = function (callback, win) {
              win = win || window;
              var messageName = "zero-timeout-message-" + postMessageId;
              exports.addListener(win, "message", function listener(e) {
                  if (e.data == messageName) {
                      exports.stopPropagation(e);
                      exports.removeListener(win, "message", listener);
                      callback();
                  }
              });
              win.postMessage(messageName, "*");
          };
      }
  
  
      exports.nextFrame = typeof window == "object" && (window.requestAnimationFrame
          || window.mozRequestAnimationFrame
          || window.webkitRequestAnimationFrame
          || window.msRequestAnimationFrame
          || window.oRequestAnimationFrame);
  
      if (exports.nextFrame)
          exports.nextFrame = exports.nextFrame.bind(window);
      else
          exports.nextFrame = function (callback) {
              setTimeout(callback, 17);
          };
  });
  