text: /*
   * OpenSeadragon - Button
   *
   * Copyright (C) 2009 CodePlex Foundation
   * Copyright (C) 2010-2013 OpenSeadragon contributors
   *
   * 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 CodePlex Foundation 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.
   */
  
  (function( $ ){
  
  /**
   * An enumeration of button states
   * @member ButtonState
   * @memberof OpenSeadragon
   * @static
   * @type {Object}
   * @property {Number} REST
   * @property {Number} GROUP
   * @property {Number} HOVER
   * @property {Number} DOWN
   */
  $.ButtonState = {
      REST:   0,
      GROUP:  1,
      HOVER:  2,
      DOWN:   3
  };
  
  /**
   * @class Button
   * @classdesc Manages events, hover states for individual buttons, tool-tips, as well
   * as fading the buttons out when the user has not interacted with them
   * for a specified period.
   *
   * @memberof OpenSeadragon
   * @extends OpenSeadragon.EventSource
   * @param {Object} options
   * @param {Element} [options.element=null] Element to use as the button. If not specified, an HTML &lt;div&gt; element is created.
   * @param {String} [options.tooltip=null] Provides context help for the button when the
   *  user hovers over it.
   * @param {String} [options.srcRest=null] URL of image to use in 'rest' state.
   * @param {String} [options.srcGroup=null] URL of image to use in 'up' state.
   * @param {String} [options.srcHover=null] URL of image to use in 'hover' state.
   * @param {String} [options.srcDown=null] URL of image to use in 'down' state.
   * @param {Number} [options.fadeDelay=0] How long to wait before fading.
   * @param {Number} [options.fadeLength=2000] How long should it take to fade the button.
   * @param {OpenSeadragon.EventHandler} [options.onPress=null] Event handler callback for {@link OpenSeadragon.Button.event:press}.
   * @param {OpenSeadragon.EventHandler} [options.onRelease=null] Event handler callback for {@link OpenSeadragon.Button.event:release}.
   * @param {OpenSeadragon.EventHandler} [options.onClick=null] Event handler callback for {@link OpenSeadragon.Button.event:click}.
   * @param {OpenSeadragon.EventHandler} [options.onEnter=null] Event handler callback for {@link OpenSeadragon.Button.event:enter}.
   * @param {OpenSeadragon.EventHandler} [options.onExit=null] Event handler callback for {@link OpenSeadragon.Button.event:exit}.
   * @param {OpenSeadragon.EventHandler} [options.onFocus=null] Event handler callback for {@link OpenSeadragon.Button.event:focus}.
   * @param {OpenSeadragon.EventHandler} [options.onBlur=null] Event handler callback for {@link OpenSeadragon.Button.event:blur}.
   * @param {Object} [options.userData=null] Arbitrary object to be passed unchanged to any attached handler methods.
   */
  $.Button = function( options ) {
  
      var _this = this;
  
      $.EventSource.call( this );
  
      $.extend( true, this, {
  
          tooltip:            null,
          srcRest:            null,
          srcGroup:           null,
          srcHover:           null,
          srcDown:            null,
          clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold,
          clickDistThreshold: $.DEFAULT_SETTINGS.clickDistThreshold,
          /**
           * How long to wait before fading.
           * @member {Number} fadeDelay
           * @memberof OpenSeadragon.Button#
           */
          fadeDelay:          0,
          /**
           * How long should it take to fade the button.
           * @member {Number} fadeLength
           * @memberof OpenSeadragon.Button#
           */
          fadeLength:         2000,
          onPress:            null,
          onRelease:          null,
          onClick:            null,
          onEnter:            null,
          onExit:             null,
          onFocus:            null,
          onBlur:             null,
          userData:           null
  
      }, options );
  
      /**
       * The button element.
       * @member {Element} element
       * @memberof OpenSeadragon.Button#
       */
      this.element = options.element || $.makeNeutralElement("div");
  
      //if the user has specified the element to bind the control to explicitly
      //then do not add the default control images
      if ( !options.element ) {
          this.imgRest      = $.makeTransparentImage( this.srcRest );
          this.imgGroup     = $.makeTransparentImage( this.srcGroup );
          this.imgHover     = $.makeTransparentImage( this.srcHover );
          this.imgDown      = $.makeTransparentImage( this.srcDown );
  
          this.imgRest.width  =
          this.imgGroup.width =
          this.imgHover.width =
          this.imgDown.width  =
              30;
  
          this.imgRest.height  =
          this.imgGroup.height =
          this.imgHover.height =
          this.imgDown.height  =
              34;
  
          this.imgRest.alt  =
          this.imgGroup.alt =
          this.imgHover.alt =
          this.imgDown.alt  =
              this.tooltip;
  
          // Allow pointer events to pass through the img elements so implicit
          //   pointer capture works on touch devices
          $.setElementPointerEventsNone( this.imgRest );
          $.setElementPointerEventsNone( this.imgGroup );
          $.setElementPointerEventsNone( this.imgHover );
          $.setElementPointerEventsNone( this.imgDown );
  
          this.element.style.position = "relative";
          $.setElementTouchActionNone( this.element );
  
          this.imgGroup.style.position =
          this.imgHover.style.position =
          this.imgDown.style.position  =
              "absolute";
  
          this.imgGroup.style.top =
          this.imgHover.style.top =
          this.imgDown.style.top  =
              "0";
  
          this.imgGroup.style.left =
          this.imgHover.style.left =
          this.imgDown.style.left  =
              "0";
  
          this.imgHover.style.visibility =
          this.imgDown.style.visibility  =
              "hidden";
  
          if ($.Browser.vendor === $.BROWSERS.FIREFOX && $.Browser.version < 3) {
              this.imgGroup.style.top =
              this.imgHover.style.top =
              this.imgDown.style.top  =
                  "";
          }
  
          this.element.appendChild( this.imgRest );
          this.element.appendChild( this.imgGroup );
          this.element.appendChild( this.imgHover );
          this.element.appendChild( this.imgDown );
      }
  
  
      this.addHandler("press", this.onPress);
      this.addHandler("release", this.onRelease);
      this.addHandler("click", this.onClick);
      this.addHandler("enter", this.onEnter);
      this.addHandler("exit", this.onExit);
      this.addHandler("focus", this.onFocus);
      this.addHandler("blur", this.onBlur);
  
      /**
       * The button's current state.
       * @member {OpenSeadragon.ButtonState} currentState
       * @memberof OpenSeadragon.Button#
       */
      this.currentState = $.ButtonState.GROUP;
  
      // When the button last began to fade.
      this.fadeBeginTime  = null;
      // Whether this button should fade after user stops interacting with the viewport.
      this.shouldFade     = false;
  
      this.element.style.display  = "inline-block";
      this.element.style.position = "relative";
      this.element.title          = this.tooltip;
  
      /**
       * Tracks mouse/touch/key events on the button.
       * @member {OpenSeadragon.MouseTracker} tracker
       * @memberof OpenSeadragon.Button#
       */
      this.tracker = new $.MouseTracker({
  
          userData:           'Button.tracker',
          element:            this.element,
          clickTimeThreshold: this.clickTimeThreshold,
          clickDistThreshold: this.clickDistThreshold,
  
          enterHandler: function( event ) {
              if ( event.insideElementPressed ) {
                  inTo( _this, $.ButtonState.DOWN );
                  /**
                   * Raised when the cursor enters the Button element.
                   *
                   * @event enter
                   * @memberof OpenSeadragon.Button
                   * @type {object}
                   * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
                   * @property {Object} originalEvent - The original DOM event.
                   * @property {?Object} userData - Arbitrary subscriber-defined object.
                   */
                  _this.raiseEvent( "enter", { originalEvent: event.originalEvent } );
              } else if ( !event.buttonDownAny ) {
                  inTo( _this, $.ButtonState.HOVER );
              }
          },
  
          focusHandler: function ( event ) {
              _this.tracker.enterHandler( event );
              /**
               * Raised when the Button element receives focus.
               *
               * @event focus
               * @memberof OpenSeadragon.Button
               * @type {object}
               * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
               * @property {Object} originalEvent - The original DOM event.
               * @property {?Object} userData - Arbitrary subscriber-defined object.
               */
              _this.raiseEvent( "focus", { originalEvent: event.originalEvent } );
          },
  
          leaveHandler: function( event ) {
              outTo( _this, $.ButtonState.GROUP );
              if ( event.insideElementPressed ) {
                  /**
                   * Raised when the cursor leaves the Button element.
                   *
                   * @event exit
                   * @memberof OpenSeadragon.Button
                   * @type {object}
                   * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
                   * @property {Object} originalEvent - The original DOM event.
                   * @property {?Object} userData - Arbitrary subscriber-defined object.
                   */
                  _this.raiseEvent( "exit", { originalEvent: event.originalEvent } );
              }
          },
  
          blurHandler: function ( event ) {
              _this.tracker.leaveHandler( event );
              /**
               * Raised when the Button element loses focus.
               *
               * @event blur
               * @memberof OpenSeadragon.Button
               * @type {object}
               * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
               * @property {Object} originalEvent - The original DOM event.
               * @property {?Object} userData - Arbitrary subscriber-defined object.
               */
              _this.raiseEvent( "blur", { originalEvent: event.originalEvent } );
          },
  
          pressHandler: function ( event ) {
              inTo( _this, $.ButtonState.DOWN );
              /**
               * Raised when a mouse button is pressed or touch occurs in the Button element.
               *
               * @event press
               * @memberof OpenSeadragon.Button
               * @type {object}
               * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
               * @property {Object} originalEvent - The original DOM event.
               * @property {?Object} userData - Arbitrary subscriber-defined object.
               */
              _this.raiseEvent( "press", { originalEvent: event.originalEvent } );
          },
  
          releaseHandler: function( event ) {
              if ( event.insideElementPressed && event.insideElementReleased ) {
                  outTo( _this, $.ButtonState.HOVER );
                  /**
                   * Raised when the mouse button is released or touch ends in the Button element.
                   *
                   * @event release
                   * @memberof OpenSeadragon.Button
                   * @type {object}
                   * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
                   * @property {Object} originalEvent - The original DOM event.
                   * @property {?Object} userData - Arbitrary subscriber-defined object.
                   */
                  _this.raiseEvent( "release", { originalEvent: event.originalEvent } );
              } else if ( event.insideElementPressed ) {
                  outTo( _this, $.ButtonState.GROUP );
              } else {
                  inTo( _this, $.ButtonState.HOVER );
              }
          },
  
          clickHandler: function( event ) {
              if ( event.quick ) {
                  /**
                   * Raised when a mouse button is pressed and released or touch is initiated and ended in the Button element within the time and distance threshold.
                   *
                   * @event click
                   * @memberof OpenSeadragon.Button
                   * @type {object}
                   * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
                   * @property {Object} originalEvent - The original DOM event.
                   * @property {?Object} userData - Arbitrary subscriber-defined object.
                   */
                  _this.raiseEvent("click", { originalEvent: event.originalEvent });
              }
          },
  
          keyHandler: function( event ){
              //console.log( "%s : handling key %s!", _this.tooltip, event.keyCode);
              if( 13 === event.keyCode ){
                  /***
                   * Raised when a mouse button is pressed and released or touch is initiated and ended in the Button element within the time and distance threshold.
                   *
                   * @event click
                   * @memberof OpenSeadragon.Button
                   * @type {object}
                   * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
                   * @property {Object} originalEvent - The original DOM event.
                   * @property {?Object} userData - Arbitrary subscriber-defined object.
                   */
                  _this.raiseEvent( "click", { originalEvent: event.originalEvent } );
                  /***
                   * Raised when the mouse button is released or touch ends in the Button element.
                   *
                   * @event release
                   * @memberof OpenSeadragon.Button
                   * @type {object}
                   * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
                   * @property {Object} originalEvent - The original DOM event.
                   * @property {?Object} userData - Arbitrary subscriber-defined object.
                   */
                  _this.raiseEvent( "release", { originalEvent: event.originalEvent } );
  
                  event.preventDefault = true;
              } else{
                  event.preventDefault = false;
              }
          }
  
      });
  
      outTo( this, $.ButtonState.REST );
  };
  
  $.extend( $.Button.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.Button.prototype */{
  
      /**
       * Used by a button container element (e.g. a ButtonGroup) to transition the button state
       * to ButtonState.GROUP.
       * @function
       */
      notifyGroupEnter: function() {
          inTo( this, $.ButtonState.GROUP );
      },
  
      /**
       * Used by a button container element (e.g. a ButtonGroup) to transition the button state
       * to ButtonState.REST.
       * @function
       */
      notifyGroupExit: function() {
          outTo( this, $.ButtonState.REST );
      },
  
      /**
       * @function
       */
      disable: function(){
          this.notifyGroupExit();
          this.element.disabled = true;
          $.setElementOpacity( this.element, 0.2, true );
      },
  
      /**
       * @function
       */
      enable: function(){
          this.element.disabled = false;
          $.setElementOpacity( this.element, 1.0, true );
          this.notifyGroupEnter();
      },
  
      destroy: function() {
          if (this.imgRest) {
              this.element.removeChild(this.imgRest);
              this.imgRest = null;
          }
          if (this.imgGroup) {
              this.element.removeChild(this.imgGroup);
              this.imgGroup = null;
          }
          if (this.imgHover) {
              this.element.removeChild(this.imgHover);
              this.imgHover = null;
          }
          if (this.imgDown) {
              this.element.removeChild(this.imgDown);
              this.imgDown = null;
          }
          this.removeAllHandlers();
          this.tracker.destroy();
          this.element = null;
      }
  
  });
  
  
  function scheduleFade( button ) {
      $.requestAnimationFrame(function(){
          updateFade( button );
      });
  }
  
  function updateFade( button ) {
      var currentTime,
          deltaTime,
          opacity;
  
      if ( button.shouldFade ) {
          currentTime = $.now();
          deltaTime   = currentTime - button.fadeBeginTime;
          opacity     = 1.0 - deltaTime / button.fadeLength;
          opacity     = Math.min( 1.0, opacity );
          opacity     = Math.max( 0.0, opacity );
  
          if( button.imgGroup ){
              $.setElementOpacity( button.imgGroup, opacity, true );
          }
          if ( opacity > 0 ) {
              // fade again
              scheduleFade( button );
          }
      }
  }
  
  function beginFading( button ) {
      button.shouldFade = true;
      button.fadeBeginTime = $.now() + button.fadeDelay;
      window.setTimeout( function(){
          scheduleFade( button );
      }, button.fadeDelay );
  }
  
  function stopFading( button ) {
      button.shouldFade = false;
      if( button.imgGroup ){
          $.setElementOpacity( button.imgGroup, 1.0, true );
      }
  }
  
  function inTo( button, newState ) {
  
      if( button.element.disabled ){
          return;
      }
  
      if ( newState >= $.ButtonState.GROUP &&
           button.currentState === $.ButtonState.REST ) {
          stopFading( button );
          button.currentState = $.ButtonState.GROUP;
      }
  
      if ( newState >= $.ButtonState.HOVER &&
           button.currentState === $.ButtonState.GROUP ) {
          if( button.imgHover ){
              button.imgHover.style.visibility = "";
          }
          button.currentState = $.ButtonState.HOVER;
      }
  
      if ( newState >= $.ButtonState.DOWN &&
           button.currentState === $.ButtonState.HOVER ) {
          if( button.imgDown ){
              button.imgDown.style.visibility = "";
          }
          button.currentState = $.ButtonState.DOWN;
      }
  }
  
  
  function outTo( button, newState ) {
  
      if( button.element.disabled ){
          return;
      }
  
      if ( newState <= $.ButtonState.HOVER &&
           button.currentState === $.ButtonState.DOWN ) {
          if( button.imgDown ){
              button.imgDown.style.visibility = "hidden";
          }
          button.currentState = $.ButtonState.HOVER;
      }
  
      if ( newState <= $.ButtonState.GROUP &&
           button.currentState === $.ButtonState.HOVER ) {
          if( button.imgHover ){
              button.imgHover.style.visibility = "hidden";
          }
          button.currentState = $.ButtonState.GROUP;
      }
  
      if ( newState <= $.ButtonState.REST &&
           button.currentState === $.ButtonState.GROUP ) {
          beginFading( button );
          button.currentState = $.ButtonState.REST;
      }
  }
  
  
  
  }( OpenSeadragon ));
  