openseadragon 0.0.9 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: aea794949446682eb81b9255301de69b7bd30709
4
- data.tar.gz: cb62202856c8db334095da210127936e5a4eb059
3
+ metadata.gz: 18d43d32894da3240f3369a3b12c81e229a61466
4
+ data.tar.gz: d58c49ef95383a9bb8feea8f3e239daa6451a7b4
5
5
  SHA512:
6
- metadata.gz: dcc726482315230456d2612a380c1ca1b2802fbe153084f33a7e0dfe9e1c15026b6cd88afbaa9fc976889f2379636b7c07fc6f01252dd5899855a698a65b1d09
7
- data.tar.gz: a9b241b6d08fe797bafb39a27be43b60e2c3e7bc79acafd603dd26c306ec56c6bdc49b2f77ec142f472387e696492bd4f9c3cc6eb082a575bbdb7a7faddfe700
6
+ metadata.gz: f70211dcbe9ee4f97e2fdcc5ea204fe13a1d16a91e0c891c61e7523874c132c489b4ffe675c83009f8e4ec26203e6199472867b844455808f8e81e3a256e92f6
7
+ data.tar.gz: 8336ab2fa8befbcc031f993d62768b1b33e32b54a441bb13c0da321adc2fb36b26ba2c2fb5e937fc2217a13edeeaa2e2587d54a63347610beffcc2c4125a6723
@@ -38,5 +38,5 @@
38
38
  };
39
39
 
40
40
  window.onload = initOpenSeadragon;
41
- document.addEventListener("page:load", initOpenSeadragon);
41
+ document.addEventListener("page:load", initOpenSeadragon, false);
42
42
  })(jQuery);
@@ -1,3 +1,3 @@
1
1
  module Openseadragon
2
- VERSION = "0.0.9"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -1,6 +1,6 @@
1
- //! OpenSeadragon 1.1.1
2
- //! Built on 2014-05-08
3
- //! Git commit: v1.1.1-0-g9aa0f32
1
+ //! OpenSeadragon 1.2.1
2
+ //! Built on 2015-01-29
3
+ //! Git commit: v1.2.1-0-2f9563d
4
4
  //! http://openseadragon.github.io
5
5
  //! License: http://openseadragon.github.io/license/
6
6
 
@@ -89,7 +89,7 @@
89
89
 
90
90
 
91
91
  /**
92
- * @version OpenSeadragon 1.1.1
92
+ * @version OpenSeadragon 1.2.1
93
93
  *
94
94
  * @file
95
95
  * <h2><strong>OpenSeadragon - Javascript Deep Zooming</strong></h2>
@@ -132,6 +132,10 @@
132
132
  * The element to append the viewer's container element to. If not provided, the 'id' property must be provided.
133
133
  * If both the element and id properties are specified, the viewer is appended to the element provided in the element property.
134
134
  *
135
+ * @property {Number} [tabIndex=0]
136
+ * Tabbing order index to assign to the viewer element. Positive values are selected in increasing order. When tabIndex is 0
137
+ * source order is used. A negative value omits the viewer from the tabbing order.
138
+ *
135
139
  * @property {Array|String|Function|Object[]|Array[]|String[]|Function[]} [tileSources=null]
136
140
  * As an Array, the tileSource can hold either Objects or mixed
137
141
  * types of Arrays of Objects, Strings, or Functions. When a value is a String,
@@ -227,6 +231,10 @@
227
231
  *
228
232
  * @property {Number} [maxZoomLevel=null]
229
233
  *
234
+ * @property {Boolean} [homeFillsViewer=false]
235
+ * Make the 'home' button fill the viewer and clip the image, instead
236
+ * of fitting the image to the viewer and letterboxing.
237
+ *
230
238
  * @property {Boolean} [panHorizontal=true]
231
239
  * Allow horizontal pan.
232
240
  *
@@ -304,6 +312,7 @@
304
312
  * @property {Boolean} [gestureSettingsMouse.flickEnabled=false] - Enable flick gesture
305
313
  * @property {Number} [gestureSettingsMouse.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
306
314
  * @property {Number} [gestureSettingsMouse.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture
315
+ * @property {Boolean} [gestureSettingsMouse.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.
307
316
  *
308
317
  * @property {OpenSeadragon.GestureSettings} [gestureSettingsTouch]
309
318
  * Settings for gestures generated by a touch pointer device. (See {@link OpenSeadragon.GestureSettings})
@@ -315,6 +324,7 @@
315
324
  * @property {Boolean} [gestureSettingsTouch.flickEnabled=true] - Enable flick gesture
316
325
  * @property {Number} [gestureSettingsTouch.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
317
326
  * @property {Number} [gestureSettingsTouch.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture
327
+ * @property {Boolean} [gestureSettingsTouch.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.
318
328
  *
319
329
  * @property {OpenSeadragon.GestureSettings} [gestureSettingsPen]
320
330
  * Settings for gestures generated by a pen pointer device. (See {@link OpenSeadragon.GestureSettings})
@@ -326,6 +336,7 @@
326
336
  * @property {Boolean} [gestureSettingsPen.flickEnabled=false] - Enable flick gesture
327
337
  * @property {Number} [gestureSettingsPen.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
328
338
  * @property {Number} [gestureSettingsPen.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture
339
+ * @property {Boolean} [gestureSettingsPen.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.
329
340
  *
330
341
  * @property {OpenSeadragon.GestureSettings} [gestureSettingsUnknown]
331
342
  * Settings for gestures generated by unknown pointer devices. (See {@link OpenSeadragon.GestureSettings})
@@ -337,6 +348,7 @@
337
348
  * @property {Boolean} [gestureSettingsUnknown.flickEnabled=true] - Enable flick gesture
338
349
  * @property {Number} [gestureSettingsUnknown.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
339
350
  * @property {Number} [gestureSettingsUnknown.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture
351
+ * @property {Boolean} [gestureSettingsUnknown.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.
340
352
  *
341
353
  * @property {Number} [zoomPerClick=2.0]
342
354
  * The "zoom distance" per mouse click or touch tap. <em><strong>Note:</strong> Setting this to 1.0 effectively disables the click-to-zoom feature (also see gestureSettings[Mouse|Touch|Pen].clickToZoom/dblClickToZoom).</em>
@@ -384,6 +396,9 @@
384
396
  * Set to false to prevent polling for navigator size changes. Useful for providing custom resize behavior.
385
397
  * Setting to false can also improve performance when the navigator is configured to a fixed size.
386
398
  *
399
+ * @property {Boolean} [navigatorRotate=true]
400
+ * If true, the navigator will be rotated together with the viewer.
401
+ *
387
402
  * @property {Number} [controlsFadeDelay=2000]
388
403
  * The number of milliseconds to wait once the user has stopped interacting
389
404
  * with the interface before begining to fade the controls. Assumes
@@ -510,10 +525,18 @@
510
525
  *
511
526
  * @property {Boolean} [preserveViewport=false]
512
527
  * If the viewer has been configured with a sequence of tile sources, then
513
- * normally navigating to through each image resets the viewport to 'home'
528
+ * normally navigating through each image resets the viewport to 'home'
514
529
  * position. If preserveViewport is set to true, then the viewport position
515
530
  * is preserved when navigating between images in the sequence.
516
531
  *
532
+ * @property {Boolean} [preserveOverlays=false]
533
+ * If the viewer has been configured with a sequence of tile sources, then
534
+ * normally navigating through each image resets the overlays.
535
+ * If preserveOverlays is set to true, then the overlays
536
+ * are preserved when navigating between images in the sequence.
537
+ * Note: setting preserveOverlays overrides any overlays specified in the
538
+ * "overlays" property.
539
+ *
517
540
  * @property {Boolean} [showReferenceStrip=false]
518
541
  * If the viewer has been configured with a sequence of tile sources, then
519
542
  * display a scrolling strip of image thumbnails for navigating through the images.
@@ -662,14 +685,12 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
662
685
  * @property {Number} revision - The revision number.
663
686
  * @since 1.0.0
664
687
  */
665
- /* jshint ignore:start */
666
688
  $.version = {
667
- versionStr: '1.1.1',
668
- major: 1,
669
- minor: 1,
670
- revision: 1
689
+ versionStr: '1.2.1',
690
+ major: parseInt('1', 10),
691
+ minor: parseInt('2', 10),
692
+ revision: parseInt('1', 10)
671
693
  };
672
- /* jshint ignore:end */
673
694
 
674
695
 
675
696
  /**
@@ -913,6 +934,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
913
934
  defaultZoomLevel: 0,
914
935
  minZoomLevel: null,
915
936
  maxZoomLevel: null,
937
+ homeFillsViewer: false,
916
938
 
917
939
  //UI RESPONSIVENESS AND FEEL
918
940
  clickTimeThreshold: 300,
@@ -921,10 +943,10 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
921
943
  dblClickDistThreshold: 20,
922
944
  springStiffness: 6.5,
923
945
  animationTime: 1.2,
924
- gestureSettingsMouse: { scrollToZoom: true, clickToZoom: true, dblClickToZoom: false, pinchToZoom: false, flickEnabled: false, flickMinSpeed: 120, flickMomentum: 0.25 },
925
- gestureSettingsTouch: { scrollToZoom: false, clickToZoom: false, dblClickToZoom: true, pinchToZoom: true, flickEnabled: true, flickMinSpeed: 120, flickMomentum: 0.25 },
926
- gestureSettingsPen: { scrollToZoom: false, clickToZoom: true, dblClickToZoom: false, pinchToZoom: false, flickEnabled: false, flickMinSpeed: 120, flickMomentum: 0.25 },
927
- gestureSettingsUnknown: { scrollToZoom: false, clickToZoom: false, dblClickToZoom: true, pinchToZoom: true, flickEnabled: true, flickMinSpeed: 120, flickMomentum: 0.25 },
946
+ gestureSettingsMouse: { scrollToZoom: true, clickToZoom: true, dblClickToZoom: false, pinchToZoom: false, flickEnabled: false, flickMinSpeed: 120, flickMomentum: 0.25, pinchRotate: false },
947
+ gestureSettingsTouch: { scrollToZoom: false, clickToZoom: false, dblClickToZoom: true, pinchToZoom: true, flickEnabled: true, flickMinSpeed: 120, flickMomentum: 0.25, pinchRotate: false },
948
+ gestureSettingsPen: { scrollToZoom: false, clickToZoom: true, dblClickToZoom: false, pinchToZoom: false, flickEnabled: false, flickMinSpeed: 120, flickMomentum: 0.25, pinchRotate: false },
949
+ gestureSettingsUnknown: { scrollToZoom: false, clickToZoom: false, dblClickToZoom: true, pinchToZoom: true, flickEnabled: true, flickMinSpeed: 120, flickMomentum: 0.25, pinchRotate: false },
928
950
  zoomPerClick: 2,
929
951
  zoomPerScroll: 1.2,
930
952
  zoomPerSecond: 1.0,
@@ -941,6 +963,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
941
963
  showSequenceControl: true, //SEQUENCE
942
964
  sequenceControlAnchor: null, //SEQUENCE
943
965
  preserveViewport: false, //SEQUENCE
966
+ preserveOverlays: false, //SEQUENCE
944
967
  navPrevNextWrap: false, //SEQUENCE
945
968
  showNavigationControl: true, //ZOOM/HOME/FULL/ROTATION
946
969
  navigationControlAnchor: null, //ZOOM/HOME/FULL/ROTATION
@@ -963,6 +986,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
963
986
  navigatorHeight: null,
964
987
  navigatorWidth: null,
965
988
  navigatorAutoResize: true,
989
+ navigatorRotate: true,
966
990
 
967
991
  // INITIAL ROTATION
968
992
  degrees: 0,
@@ -1593,6 +1617,21 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
1593
1617
  },
1594
1618
 
1595
1619
 
1620
+ /**
1621
+ * Sets the specified element's touch-action style attribute to 'none'.
1622
+ * @function
1623
+ * @param {Element|String} element
1624
+ */
1625
+ setElementTouchActionNone: function( element ) {
1626
+ element = $.getElement( element );
1627
+ if ( typeof element.style.touchAction !== 'undefined' ) {
1628
+ element.style.touchAction = 'none';
1629
+ } else if ( typeof element.style.msTouchAction !== 'undefined' ) {
1630
+ element.style.msTouchAction = 'none';
1631
+ }
1632
+ },
1633
+
1634
+
1596
1635
  /**
1597
1636
  * Add the specified CSS class to the element if not present.
1598
1637
  * @function
@@ -2167,7 +2206,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
2167
2206
  )
2168
2207
  );
2169
2208
  } else {
2170
- regex = new RegExp( "Trident/.*rv:([0-9]{1,}[.0-9]{0,}) ");
2209
+ regex = new RegExp( "Trident/.*rv:([0-9]{1,}[.0-9]{0,})");
2171
2210
  if ( regex.exec( ua ) !== null ) {
2172
2211
  $.Browser.vendor = $.BROWSERS.IE;
2173
2212
  $.Browser.version = parseFloat( RegExp.$1 );
@@ -2874,7 +2913,10 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
2874
2913
 
2875
2914
  (function ( $ ) {
2876
2915
 
2877
- // dictionary from hash to private properties
2916
+ // All MouseTracker instances
2917
+ var MOUSETRACKERS = [];
2918
+
2919
+ // dictionary from hash to private properties
2878
2920
  var THIS = {};
2879
2921
 
2880
2922
 
@@ -2891,6 +2933,9 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
2891
2933
  * @param {Element|String} options.element
2892
2934
  * A reference to an element or an element id for which the pointer/key
2893
2935
  * events will be monitored.
2936
+ * @param {Boolean} [options.startDisabled=false]
2937
+ * If true, event tracking on the element will not start until
2938
+ * {@link OpenSeadragon.MouseTracker.setTracking|setTracking} is called.
2894
2939
  * @param {Number} options.clickTimeThreshold
2895
2940
  * The number of milliseconds within which a pointer down-up event combination
2896
2941
  * will be treated as a click gesture.
@@ -2912,8 +2957,12 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
2912
2957
  * An optional handler for pointer exit.
2913
2958
  * @param {OpenSeadragon.EventHandler} [options.pressHandler=null]
2914
2959
  * An optional handler for pointer press.
2960
+ * @param {OpenSeadragon.EventHandler} [options.nonPrimaryPressHandler=null]
2961
+ * An optional handler for pointer non-primary button press.
2915
2962
  * @param {OpenSeadragon.EventHandler} [options.releaseHandler=null]
2916
2963
  * An optional handler for pointer release.
2964
+ * @param {OpenSeadragon.EventHandler} [options.nonPrimaryReleaseHandler=null]
2965
+ * An optional handler for pointer non-primary button release.
2917
2966
  * @param {OpenSeadragon.EventHandler} [options.moveHandler=null]
2918
2967
  * An optional handler for pointer move.
2919
2968
  * @param {OpenSeadragon.EventHandler} [options.scrollHandler=null]
@@ -2928,6 +2977,10 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
2928
2977
  * An optional handler for after a drag gesture.
2929
2978
  * @param {OpenSeadragon.EventHandler} [options.pinchHandler=null]
2930
2979
  * An optional handler for the pinch gesture.
2980
+ * @param {OpenSeadragon.EventHandler} [options.keyDownHandler=null]
2981
+ * An optional handler for keydown.
2982
+ * @param {OpenSeadragon.EventHandler} [options.keyUpHandler=null]
2983
+ * An optional handler for keyup.
2931
2984
  * @param {OpenSeadragon.EventHandler} [options.keyHandler=null]
2932
2985
  * An optional handler for keypress.
2933
2986
  * @param {OpenSeadragon.EventHandler} [options.focusHandler=null]
@@ -2939,6 +2992,8 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
2939
2992
  */
2940
2993
  $.MouseTracker = function ( options ) {
2941
2994
 
2995
+ MOUSETRACKERS.push( this );
2996
+
2942
2997
  var args = arguments;
2943
2998
 
2944
2999
  if ( !$.isPlainObject( options ) ) {
@@ -2962,46 +3017,50 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
2962
3017
  * @member {Number} clickTimeThreshold
2963
3018
  * @memberof OpenSeadragon.MouseTracker#
2964
3019
  */
2965
- this.clickTimeThreshold = options.clickTimeThreshold;
3020
+ this.clickTimeThreshold = options.clickTimeThreshold || $.DEFAULT_SETTINGS.clickTimeThreshold;
2966
3021
  /**
2967
3022
  * The maximum distance allowed between a pointer down event and a pointer up event
2968
3023
  * to be treated as a click gesture.
2969
3024
  * @member {Number} clickDistThreshold
2970
3025
  * @memberof OpenSeadragon.MouseTracker#
2971
3026
  */
2972
- this.clickDistThreshold = options.clickDistThreshold;
3027
+ this.clickDistThreshold = options.clickDistThreshold || $.DEFAULT_SETTINGS.clickDistThreshold;
2973
3028
  /**
2974
3029
  * The number of milliseconds within which two pointer down-up event combinations
2975
3030
  * will be treated as a double-click gesture.
2976
3031
  * @member {Number} dblClickTimeThreshold
2977
3032
  * @memberof OpenSeadragon.MouseTracker#
2978
3033
  */
2979
- this.dblClickTimeThreshold = options.dblClickTimeThreshold;
3034
+ this.dblClickTimeThreshold = options.dblClickTimeThreshold || $.DEFAULT_SETTINGS.dblClickTimeThreshold;
2980
3035
  /**
2981
3036
  * The maximum distance allowed between two pointer click events
2982
3037
  * to be treated as a click gesture.
2983
3038
  * @member {Number} clickDistThreshold
2984
3039
  * @memberof OpenSeadragon.MouseTracker#
2985
3040
  */
2986
- this.dblClickDistThreshold = options.dblClickDistThreshold;
2987
- this.userData = options.userData || null;
2988
- this.stopDelay = options.stopDelay || 50;
2989
-
2990
- this.enterHandler = options.enterHandler || null;
2991
- this.exitHandler = options.exitHandler || null;
2992
- this.pressHandler = options.pressHandler || null;
2993
- this.releaseHandler = options.releaseHandler || null;
2994
- this.moveHandler = options.moveHandler || null;
2995
- this.scrollHandler = options.scrollHandler || null;
2996
- this.clickHandler = options.clickHandler || null;
2997
- this.dblClickHandler = options.dblClickHandler || null;
2998
- this.dragHandler = options.dragHandler || null;
2999
- this.dragEndHandler = options.dragEndHandler || null;
3000
- this.pinchHandler = options.pinchHandler || null;
3001
- this.stopHandler = options.stopHandler || null;
3002
- this.keyHandler = options.keyHandler || null;
3003
- this.focusHandler = options.focusHandler || null;
3004
- this.blurHandler = options.blurHandler || null;
3041
+ this.dblClickDistThreshold = options.dblClickDistThreshold || $.DEFAULT_SETTINGS.dblClickDistThreshold;
3042
+ this.userData = options.userData || null;
3043
+ this.stopDelay = options.stopDelay || 50;
3044
+
3045
+ this.enterHandler = options.enterHandler || null;
3046
+ this.exitHandler = options.exitHandler || null;
3047
+ this.pressHandler = options.pressHandler || null;
3048
+ this.nonPrimaryPressHandler = options.nonPrimaryPressHandler || null;
3049
+ this.releaseHandler = options.releaseHandler || null;
3050
+ this.nonPrimaryReleaseHandler = options.nonPrimaryReleaseHandler || null;
3051
+ this.moveHandler = options.moveHandler || null;
3052
+ this.scrollHandler = options.scrollHandler || null;
3053
+ this.clickHandler = options.clickHandler || null;
3054
+ this.dblClickHandler = options.dblClickHandler || null;
3055
+ this.dragHandler = options.dragHandler || null;
3056
+ this.dragEndHandler = options.dragEndHandler || null;
3057
+ this.pinchHandler = options.pinchHandler || null;
3058
+ this.stopHandler = options.stopHandler || null;
3059
+ this.keyDownHandler = options.keyDownHandler || null;
3060
+ this.keyUpHandler = options.keyUpHandler || null;
3061
+ this.keyHandler = options.keyHandler || null;
3062
+ this.focusHandler = options.focusHandler || null;
3063
+ this.blurHandler = options.blurHandler || null;
3005
3064
 
3006
3065
  //Store private properties in a scope sealed hash map
3007
3066
  var _this = this;
@@ -3010,12 +3069,12 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3010
3069
  * @private
3011
3070
  * @property {Boolean} tracking
3012
3071
  * Are we currently tracking pointer events for this element.
3013
- * @property {Boolean} capturing
3014
- * Are we curruently capturing mouse events (legacy mouse events only).
3015
3072
  */
3016
3073
  THIS[ this.hash ] = {
3017
3074
  click: function ( event ) { onClick( _this, event ); },
3018
3075
  dblclick: function ( event ) { onDblClick( _this, event ); },
3076
+ keydown: function ( event ) { onKeyDown( _this, event ); },
3077
+ keyup: function ( event ) { onKeyUp( _this, event ); },
3019
3078
  keypress: function ( event ) { onKeyPress( _this, event ); },
3020
3079
  focus: function ( event ) { onFocus( _this, event ); },
3021
3080
  blur: function ( event ) { onBlur( _this, event ); },
@@ -3025,30 +3084,30 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3025
3084
  DOMMouseScroll: function ( event ) { onMouseWheel( _this, event ); },
3026
3085
  MozMousePixelScroll: function ( event ) { onMouseWheel( _this, event ); },
3027
3086
 
3087
+ mouseenter: function ( event ) { onMouseEnter( _this, event ); }, // Used on IE8 only
3088
+ mouseleave: function ( event ) { onMouseLeave( _this, event ); }, // Used on IE8 only
3028
3089
  mouseover: function ( event ) { onMouseOver( _this, event ); },
3029
3090
  mouseout: function ( event ) { onMouseOut( _this, event ); },
3030
- mouseenter: function ( event ) { onMouseEnter( _this, event ); },
3031
- mouseleave: function ( event ) { onMouseLeave( _this, event ); },
3032
3091
  mousedown: function ( event ) { onMouseDown( _this, event ); },
3033
3092
  mouseup: function ( event ) { onMouseUp( _this, event ); },
3034
3093
  mouseupcaptured: function ( event ) { onMouseUpCaptured( _this, event ); },
3035
3094
  mousemove: function ( event ) { onMouseMove( _this, event ); },
3036
3095
  mousemovecaptured: function ( event ) { onMouseMoveCaptured( _this, event ); },
3037
3096
 
3038
- touchenter: function ( event ) { onTouchEnter( _this, event ); },
3039
- touchleave: function ( event ) { onTouchLeave( _this, event ); },
3040
3097
  touchstart: function ( event ) { onTouchStart( _this, event ); },
3041
3098
  touchend: function ( event ) { onTouchEnd( _this, event ); },
3099
+ touchendcaptured: function ( event ) { onTouchEndCaptured( _this, event ); },
3042
3100
  touchmove: function ( event ) { onTouchMove( _this, event ); },
3101
+ touchmovecaptured: function ( event ) { onTouchMoveCaptured( _this, event ); },
3043
3102
  touchcancel: function ( event ) { onTouchCancel( _this, event ); },
3044
3103
 
3045
3104
  gesturestart: function ( event ) { onGestureStart( _this, event ); },
3046
3105
  gesturechange: function ( event ) { onGestureChange( _this, event ); },
3047
3106
 
3048
- pointerenter: function ( event ) { onPointerEnter( _this, event ); },
3049
- MSPointerEnter: function ( event ) { onPointerEnter( _this, event ); },
3050
- pointerleave: function ( event ) { onPointerLeave( _this, event ); },
3051
- MSPointerLeave: function ( event ) { onPointerLeave( _this, event ); },
3107
+ pointerover: function ( event ) { onPointerOver( _this, event ); },
3108
+ MSPointerOver: function ( event ) { onPointerOver( _this, event ); },
3109
+ pointerout: function ( event ) { onPointerOut( _this, event ); },
3110
+ MSPointerOut: function ( event ) { onPointerOut( _this, event ); },
3052
3111
  pointerdown: function ( event ) { onPointerDown( _this, event ); },
3053
3112
  MSPointerDown: function ( event ) { onPointerDown( _this, event ); },
3054
3113
  pointerup: function ( event ) { onPointerUp( _this, event ); },
@@ -3057,6 +3116,8 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3057
3116
  MSPointerMove: function ( event ) { onPointerMove( _this, event ); },
3058
3117
  pointercancel: function ( event ) { onPointerCancel( _this, event ); },
3059
3118
  MSPointerCancel: function ( event ) { onPointerCancel( _this, event ); },
3119
+ pointerupcaptured: function ( event ) { onPointerUpCaptured( _this, event ); },
3120
+ pointermovecaptured: function ( event ) { onPointerMoveCaptured( _this, event ); },
3060
3121
 
3061
3122
  tracking: false,
3062
3123
 
@@ -3066,9 +3127,6 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3066
3127
  // of the element (for hover-capable devices) and/or have contact or a button press initiated in the element.
3067
3128
  activePointersLists: [],
3068
3129
 
3069
- // Legacy mouse event tracking
3070
- capturing: false,
3071
-
3072
3130
  // Tracking for double-click gesture
3073
3131
  lastClickPos: null,
3074
3132
  dblClickTimeOut: null,
@@ -3081,6 +3139,9 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3081
3139
  currentPinchCenter: null
3082
3140
  };
3083
3141
 
3142
+ if ( !options.startDisabled ) {
3143
+ this.setTracking( true );
3144
+ }
3084
3145
  };
3085
3146
 
3086
3147
  $.MouseTracker.prototype = /** @lends OpenSeadragon.MouseTracker.prototype */{
@@ -3090,8 +3151,20 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3090
3151
  * @function
3091
3152
  */
3092
3153
  destroy: function () {
3154
+ var i;
3155
+
3093
3156
  stopTracking( this );
3094
3157
  this.element = null;
3158
+
3159
+ for ( i = 0; i < MOUSETRACKERS.length; i++ ) {
3160
+ if ( MOUSETRACKERS[ i ] === this ) {
3161
+ MOUSETRACKERS.splice( i, 1 );
3162
+ break;
3163
+ }
3164
+ }
3165
+
3166
+ THIS[ this.hash ] = null;
3167
+ delete THIS[ this.hash ];
3095
3168
  },
3096
3169
 
3097
3170
  /**
@@ -3144,6 +3217,24 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3144
3217
  return list;
3145
3218
  },
3146
3219
 
3220
+ /**
3221
+ * Returns the total number of pointers currently active on the tracked element.
3222
+ * @function
3223
+ * @returns {Number}
3224
+ */
3225
+ getActivePointerCount: function () {
3226
+ var delegate = THIS[ this.hash ],
3227
+ i,
3228
+ len = delegate.activePointersLists.length,
3229
+ count = 0;
3230
+
3231
+ for ( i = 0; i < len; i++ ) {
3232
+ count += delegate.activePointersLists[ i ].getLength();
3233
+ }
3234
+
3235
+ return count;
3236
+ },
3237
+
3147
3238
  /**
3148
3239
  * Implement or assign implementation to these handlers during or after
3149
3240
  * calling the constructor.
@@ -3158,6 +3249,8 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3158
3249
  * @param {Number} event.buttons
3159
3250
  * Current buttons pressed.
3160
3251
  * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
3252
+ * @param {Number} event.pointers
3253
+ * Number of pointers (all types) active in the tracked element.
3161
3254
  * @param {Boolean} event.insideElementPressed
3162
3255
  * True if the left mouse button is currently being pressed and was
3163
3256
  * initiated inside the tracked element, otherwise false.
@@ -3188,6 +3281,8 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3188
3281
  * @param {Number} event.buttons
3189
3282
  * Current buttons pressed.
3190
3283
  * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
3284
+ * @param {Number} event.pointers
3285
+ * Number of pointers (all types) active in the tracked element.
3191
3286
  * @param {Boolean} event.insideElementPressed
3192
3287
  * True if the left mouse button is currently being pressed and was
3193
3288
  * initiated inside the tracked element, otherwise false.
@@ -3229,6 +3324,34 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3229
3324
  */
3230
3325
  pressHandler: function () { },
3231
3326
 
3327
+ /**
3328
+ * Implement or assign implementation to these handlers during or after
3329
+ * calling the constructor.
3330
+ * @function
3331
+ * @param {Object} event
3332
+ * @param {OpenSeadragon.MouseTracker} event.eventSource
3333
+ * A reference to the tracker instance.
3334
+ * @param {String} event.pointerType
3335
+ * "mouse", "touch", "pen", etc.
3336
+ * @param {OpenSeadragon.Point} event.position
3337
+ * The position of the event relative to the tracked element.
3338
+ * @param {Number} event.button
3339
+ * Button which caused the event.
3340
+ * -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
3341
+ * @param {Number} event.buttons
3342
+ * Current buttons pressed.
3343
+ * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
3344
+ * @param {Boolean} event.isTouchEvent
3345
+ * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
3346
+ * @param {Object} event.originalEvent
3347
+ * The original event object.
3348
+ * @param {Boolean} event.preventDefaultAction
3349
+ * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3350
+ * @param {Object} event.userData
3351
+ * Arbitrary user-defined object.
3352
+ */
3353
+ nonPrimaryPressHandler: function () { },
3354
+
3232
3355
  /**
3233
3356
  * Implement or assign implementation to these handlers during or after
3234
3357
  * calling the constructor.
@@ -3259,6 +3382,34 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3259
3382
  */
3260
3383
  releaseHandler: function () { },
3261
3384
 
3385
+ /**
3386
+ * Implement or assign implementation to these handlers during or after
3387
+ * calling the constructor.
3388
+ * @function
3389
+ * @param {Object} event
3390
+ * @param {OpenSeadragon.MouseTracker} event.eventSource
3391
+ * A reference to the tracker instance.
3392
+ * @param {String} event.pointerType
3393
+ * "mouse", "touch", "pen", etc.
3394
+ * @param {OpenSeadragon.Point} event.position
3395
+ * The position of the event relative to the tracked element.
3396
+ * @param {Number} event.button
3397
+ * Button which caused the event.
3398
+ * -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
3399
+ * @param {Number} event.buttons
3400
+ * Current buttons pressed.
3401
+ * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
3402
+ * @param {Boolean} event.isTouchEvent
3403
+ * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
3404
+ * @param {Object} event.originalEvent
3405
+ * The original event object.
3406
+ * @param {Boolean} event.preventDefaultAction
3407
+ * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3408
+ * @param {Object} event.userData
3409
+ * Arbitrary user-defined object.
3410
+ */
3411
+ nonPrimaryReleaseHandler: function () { },
3412
+
3262
3413
  /**
3263
3414
  * Implement or assign implementation to these handlers during or after
3264
3415
  * calling the constructor.
@@ -3485,8 +3636,66 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3485
3636
  * A reference to the tracker instance.
3486
3637
  * @param {Number} event.keyCode
3487
3638
  * The key code that was pressed.
3639
+ * @param {Boolean} event.ctrl
3640
+ * True if the ctrl key was pressed during this event.
3641
+ * @param {Boolean} event.shift
3642
+ * True if the shift key was pressed during this event.
3643
+ * @param {Boolean} event.alt
3644
+ * True if the alt key was pressed during this event.
3645
+ * @param {Boolean} event.meta
3646
+ * True if the meta key was pressed during this event.
3647
+ * @param {Object} event.originalEvent
3648
+ * The original event object.
3649
+ * @param {Boolean} event.preventDefaultAction
3650
+ * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3651
+ * @param {Object} event.userData
3652
+ * Arbitrary user-defined object.
3653
+ */
3654
+ keyDownHandler: function () { },
3655
+
3656
+ /**
3657
+ * Implement or assign implementation to these handlers during or after
3658
+ * calling the constructor.
3659
+ * @function
3660
+ * @param {Object} event
3661
+ * @param {OpenSeadragon.MouseTracker} event.eventSource
3662
+ * A reference to the tracker instance.
3663
+ * @param {Number} event.keyCode
3664
+ * The key code that was pressed.
3665
+ * @param {Boolean} event.ctrl
3666
+ * True if the ctrl key was pressed during this event.
3667
+ * @param {Boolean} event.shift
3668
+ * True if the shift key was pressed during this event.
3669
+ * @param {Boolean} event.alt
3670
+ * True if the alt key was pressed during this event.
3671
+ * @param {Boolean} event.meta
3672
+ * True if the meta key was pressed during this event.
3673
+ * @param {Object} event.originalEvent
3674
+ * The original event object.
3675
+ * @param {Boolean} event.preventDefaultAction
3676
+ * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3677
+ * @param {Object} event.userData
3678
+ * Arbitrary user-defined object.
3679
+ */
3680
+ keyUpHandler: function () { },
3681
+
3682
+ /**
3683
+ * Implement or assign implementation to these handlers during or after
3684
+ * calling the constructor.
3685
+ * @function
3686
+ * @param {Object} event
3687
+ * @param {OpenSeadragon.MouseTracker} event.eventSource
3688
+ * A reference to the tracker instance.
3689
+ * @param {Number} event.keyCode
3690
+ * The key code that was pressed.
3691
+ * @param {Boolean} event.ctrl
3692
+ * True if the ctrl key was pressed during this event.
3488
3693
  * @param {Boolean} event.shift
3489
3694
  * True if the shift key was pressed during this event.
3695
+ * @param {Boolean} event.alt
3696
+ * True if the alt key was pressed during this event.
3697
+ * @param {Boolean} event.meta
3698
+ * True if the meta key was pressed during this event.
3490
3699
  * @param {Object} event.originalEvent
3491
3700
  * The original event object.
3492
3701
  * @param {Boolean} event.preventDefaultAction
@@ -3625,6 +3834,8 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3625
3834
  // Pointer event model and feature detection
3626
3835
  ///////////////////////////////////////////////////////////////////////////////
3627
3836
 
3837
+ $.MouseTracker.captureElement = document;
3838
+
3628
3839
  /**
3629
3840
  * Detect available mouse wheel event name.
3630
3841
  */
@@ -3644,54 +3855,56 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3644
3855
  /**
3645
3856
  * Detect browser pointer device event model(s) and build appropriate list of events to subscribe to.
3646
3857
  */
3647
- $.MouseTracker.subscribeEvents = [ "click", "dblclick", "keypress", "focus", "blur", $.MouseTracker.wheelEventName ];
3858
+ $.MouseTracker.subscribeEvents = [ "click", "dblclick", "keydown", "keyup", "keypress", "focus", "blur", $.MouseTracker.wheelEventName ];
3648
3859
 
3649
3860
  if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) {
3650
3861
  // Older Firefox
3651
3862
  $.MouseTracker.subscribeEvents.push( "MozMousePixelScroll" );
3652
3863
  }
3653
3864
 
3654
- if ( window.PointerEvent ) {
3865
+ // Note: window.navigator.pointerEnable is deprecated on IE 11 and not part of W3C spec.
3866
+ if ( window.PointerEvent && ( window.navigator.pointerEnabled || $.Browser.vendor !== $.BROWSERS.IE ) ) {
3655
3867
  // IE11 and other W3C Pointer Event implementations (see http://www.w3.org/TR/pointerevents)
3656
- $.MouseTracker.subscribeEvents.push( "pointerenter", "pointerleave", "pointerdown", "pointerup", "pointermove", "pointercancel" );
3868
+ $.MouseTracker.havePointerEvents = true;
3869
+ $.MouseTracker.subscribeEvents.push( "pointerover", "pointerout", "pointerdown", "pointerup", "pointermove", "pointercancel" );
3657
3870
  $.MouseTracker.unprefixedPointerEvents = true;
3658
3871
  if( navigator.maxTouchPoints ) {
3659
3872
  $.MouseTracker.maxTouchPoints = navigator.maxTouchPoints;
3660
3873
  } else {
3661
3874
  $.MouseTracker.maxTouchPoints = 0;
3662
3875
  }
3663
- $.MouseTracker.haveTouchEnter = true;
3664
- $.MouseTracker.haveMouseEnter = true;
3665
- } else if ( window.MSPointerEvent ) {
3876
+ $.MouseTracker.haveMouseEnter = false;
3877
+ } else if ( window.MSPointerEvent && window.navigator.msPointerEnabled ) {
3666
3878
  // IE10
3667
- $.MouseTracker.subscribeEvents.push( "MSPointerEnter", "MSPointerLeave", "MSPointerDown", "MSPointerUp", "MSPointerMove", "MSPointerCancel" );
3879
+ $.MouseTracker.havePointerEvents = true;
3880
+ $.MouseTracker.subscribeEvents.push( "MSPointerOver", "MSPointerOut", "MSPointerDown", "MSPointerUp", "MSPointerMove", "MSPointerCancel" );
3668
3881
  $.MouseTracker.unprefixedPointerEvents = false;
3669
3882
  if( navigator.msMaxTouchPoints ) {
3670
3883
  $.MouseTracker.maxTouchPoints = navigator.msMaxTouchPoints;
3671
3884
  } else {
3672
3885
  $.MouseTracker.maxTouchPoints = 0;
3673
3886
  }
3674
- $.MouseTracker.haveTouchEnter = true;
3675
- $.MouseTracker.haveMouseEnter = true;
3887
+ $.MouseTracker.haveMouseEnter = false;
3676
3888
  } else {
3677
3889
  // Legacy W3C mouse events
3678
- // TODO: Favor mouseenter/mouseleave over mouseover/mouseout when Webkit browser support is better
3679
- $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout", "mousedown", "mouseup", "mousemove" );
3680
- $.MouseTracker.haveMouseEnter = false;
3890
+ $.MouseTracker.havePointerEvents = false;
3891
+ if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
3892
+ $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" );
3893
+ $.MouseTracker.haveMouseEnter = true;
3894
+ } else {
3895
+ $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout" );
3896
+ $.MouseTracker.haveMouseEnter = false;
3897
+ }
3898
+ $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" );
3681
3899
  if ( 'ontouchstart' in window ) {
3682
- // iOS, Android, and other W3c Touch Event implementations (see http://www.w3.org/TR/2011/WD-touch-events-20110505)
3900
+ // iOS, Android, and other W3c Touch Event implementations
3901
+ // (see http://www.w3.org/TR/touch-events/)
3902
+ // (see https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html)
3903
+ // (see https://developer.apple.com/library/safari/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html)
3683
3904
  $.MouseTracker.subscribeEvents.push( "touchstart", "touchend", "touchmove", "touchcancel" );
3684
- if ( 'ontouchenter' in window ) {
3685
- $.MouseTracker.subscribeEvents.push( "touchenter", "touchleave" );
3686
- $.MouseTracker.haveTouchEnter = true;
3687
- } else {
3688
- $.MouseTracker.haveTouchEnter = false;
3689
- }
3690
- } else {
3691
- $.MouseTracker.haveTouchEnter = false;
3692
3905
  }
3693
3906
  if ( 'ongesturestart' in window ) {
3694
- // iOS (see https://developer.apple.com/library/safari/documentation/UserExperience/Reference/GestureEventClassReference/GestureEvent/GestureEvent.html)
3907
+ // iOS (see https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html)
3695
3908
  // Subscribe to these to prevent default gesture handling
3696
3909
  $.MouseTracker.subscribeEvents.push( "gesturestart", "gesturechange" );
3697
3910
  }
@@ -3776,6 +3989,12 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3776
3989
  * @memberof OpenSeadragon.MouseTracker.GesturePointList#
3777
3990
  */
3778
3991
  this.clicks = 0;
3992
+ /**
3993
+ * Current number of captured pointers for the device.
3994
+ * @member {Number} captureCount
3995
+ * @memberof OpenSeadragon.MouseTracker.GesturePointList#
3996
+ */
3997
+ this.captureCount = 0;
3779
3998
  };
3780
3999
  $.MouseTracker.GesturePointList.prototype = /** @lends OpenSeadragon.MouseTracker.GesturePointList.prototype */{
3781
4000
  /**
@@ -3864,6 +4083,64 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3864
4083
  // Utility functions
3865
4084
  ///////////////////////////////////////////////////////////////////////////////
3866
4085
 
4086
+ /**
4087
+ * Removes all tracked pointers.
4088
+ * @private
4089
+ * @inner
4090
+ */
4091
+ function clearTrackedPointers( tracker ) {
4092
+ var delegate = THIS[ tracker.hash ],
4093
+ i,
4094
+ pointerListCount = delegate.activePointersLists.length;
4095
+
4096
+ for ( i = 0; i < pointerListCount; i++ ) {
4097
+ if ( delegate.activePointersLists[ i ].captureCount > 0 ) {
4098
+ $.removeEvent(
4099
+ $.MouseTracker.captureElement,
4100
+ 'mousemove',
4101
+ delegate.mousemovecaptured,
4102
+ true
4103
+ );
4104
+ $.removeEvent(
4105
+ $.MouseTracker.captureElement,
4106
+ 'mouseup',
4107
+ delegate.mouseupcaptured,
4108
+ true
4109
+ );
4110
+ $.removeEvent(
4111
+ $.MouseTracker.captureElement,
4112
+ $.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove',
4113
+ delegate.pointermovecaptured,
4114
+ true
4115
+ );
4116
+ $.removeEvent(
4117
+ $.MouseTracker.captureElement,
4118
+ $.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp',
4119
+ delegate.pointerupcaptured,
4120
+ true
4121
+ );
4122
+ $.removeEvent(
4123
+ $.MouseTracker.captureElement,
4124
+ 'touchmove',
4125
+ delegate.touchmovecaptured,
4126
+ true
4127
+ );
4128
+ $.removeEvent(
4129
+ $.MouseTracker.captureElement,
4130
+ 'touchend',
4131
+ delegate.touchendcaptured,
4132
+ true
4133
+ );
4134
+
4135
+ delegate.activePointersLists[ i ].captureCount = 0;
4136
+ }
4137
+ }
4138
+
4139
+ for ( i = 0; i < pointerListCount; i++ ) {
4140
+ delegate.activePointersLists.pop();
4141
+ }
4142
+ }
4143
+
3867
4144
  /**
3868
4145
  * Starts tracking pointer events on the tracked element.
3869
4146
  * @private
@@ -3884,6 +4161,9 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3884
4161
  false
3885
4162
  );
3886
4163
  }
4164
+
4165
+ clearTrackedPointers( tracker );
4166
+
3887
4167
  delegate.tracking = true;
3888
4168
  }
3889
4169
  }
@@ -3909,73 +4189,111 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3909
4189
  );
3910
4190
  }
3911
4191
 
3912
- releaseMouse( tracker );
4192
+ clearTrackedPointers( tracker );
4193
+
3913
4194
  delegate.tracking = false;
3914
4195
  }
3915
4196
  }
3916
4197
 
3917
4198
  /**
3918
- * Begin capturing mouse events to the tracked element (legacy mouse events only).
3919
4199
  * @private
3920
4200
  * @inner
3921
4201
  */
3922
- function captureMouse( tracker ) {
4202
+ function getCaptureEventParams( tracker, pointerType ) {
3923
4203
  var delegate = THIS[ tracker.hash ];
3924
4204
 
3925
- if ( !delegate.capturing ) {
3926
- if ( $.MouseTracker.supportsMouseCapture ) {
3927
- // IE<10, Firefox, other browsers with setCapture()/releaseCapture()
4205
+ if ( pointerType === 'pointerevent' ) {
4206
+ return {
4207
+ upName: $.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp',
4208
+ upHandler: delegate.pointerupcaptured,
4209
+ moveName: $.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove',
4210
+ moveHandler: delegate.pointermovecaptured
4211
+ };
4212
+ } else if ( pointerType === 'mouse' ) {
4213
+ return {
4214
+ upName: 'mouseup',
4215
+ upHandler: delegate.mouseupcaptured,
4216
+ moveName: 'mousemove',
4217
+ moveHandler: delegate.mousemovecaptured
4218
+ };
4219
+ } else if ( pointerType === 'touch' ) {
4220
+ return {
4221
+ upName: 'touchend',
4222
+ upHandler: delegate.touchendcaptured,
4223
+ moveName: 'touchmove',
4224
+ moveHandler: delegate.touchmovecaptured
4225
+ };
4226
+ } else {
4227
+ throw new Error( "MouseTracker.getCaptureEventParams: Unknown pointer type." );
4228
+ }
4229
+ }
4230
+
4231
+ /**
4232
+ * Begin capturing pointer events to the tracked element.
4233
+ * @private
4234
+ * @inner
4235
+ */
4236
+ function capturePointer( tracker, pointerType ) {
4237
+ var pointsList = tracker.getActivePointersListByType( pointerType ),
4238
+ eventParams;
4239
+
4240
+ pointsList.captureCount++;
4241
+
4242
+ if ( pointsList.captureCount === 1 ) {
4243
+ if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
3928
4244
  tracker.element.setCapture( true );
3929
4245
  } else {
3930
- // For browsers without setCapture()/releaseCapture(), we emulate mouse capture by hanging listeners on the window object.
4246
+ eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : pointerType );
4247
+ // We emulate mouse capture by hanging listeners on the document object.
3931
4248
  // (Note we listen on the capture phase so the captured handlers will get called first)
3932
4249
  $.addEvent(
3933
- window,
3934
- "mouseup",
3935
- delegate.mouseupcaptured,
4250
+ $.MouseTracker.captureElement,
4251
+ eventParams.upName,
4252
+ eventParams.upHandler,
3936
4253
  true
3937
4254
  );
3938
4255
  $.addEvent(
3939
- window,
3940
- "mousemove",
3941
- delegate.mousemovecaptured,
4256
+ $.MouseTracker.captureElement,
4257
+ eventParams.moveName,
4258
+ eventParams.moveHandler,
3942
4259
  true
3943
4260
  );
3944
4261
  }
3945
- delegate.capturing = true;
3946
4262
  }
3947
4263
  }
3948
4264
 
3949
4265
 
3950
4266
  /**
3951
- * Stop capturing mouse events to the tracked element (legacy mouse events only).
4267
+ * Stop capturing pointer events to the tracked element.
3952
4268
  * @private
3953
4269
  * @inner
3954
4270
  */
3955
- function releaseMouse( tracker ) {
3956
- var delegate = THIS[ tracker.hash ];
4271
+ function releasePointer( tracker, pointerType ) {
4272
+ var pointsList = tracker.getActivePointersListByType( pointerType ),
4273
+ eventParams;
3957
4274
 
3958
- if ( delegate.capturing ) {
3959
- if ( $.MouseTracker.supportsMouseCapture ) {
3960
- // IE<10, Firefox, other browsers with setCapture()/releaseCapture()
4275
+ pointsList.captureCount--;
4276
+
4277
+ if ( pointsList.captureCount === 0 ) {
4278
+ if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
3961
4279
  tracker.element.releaseCapture();
3962
4280
  } else {
3963
- // For browsers without setCapture()/releaseCapture(), we emulate mouse capture by hanging listeners on the window object.
4281
+ eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : pointerType );
4282
+ // We emulate mouse capture by hanging listeners on the document object.
3964
4283
  // (Note we listen on the capture phase so the captured handlers will get called first)
3965
4284
  $.removeEvent(
3966
- window,
3967
- "mousemove",
3968
- delegate.mousemovecaptured,
4285
+ $.MouseTracker.captureElement,
4286
+ eventParams.moveName,
4287
+ eventParams.moveHandler,
3969
4288
  true
3970
4289
  );
3971
4290
  $.removeEvent(
3972
- window,
3973
- "mouseup",
3974
- delegate.mouseupcaptured,
4291
+ $.MouseTracker.captureElement,
4292
+ eventParams.upName,
4293
+ eventParams.upHandler,
3975
4294
  true
3976
4295
  );
3977
4296
  }
3978
- delegate.capturing = false;
3979
4297
  }
3980
4298
  }
3981
4299
 
@@ -4074,21 +4392,81 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4074
4392
  }
4075
4393
 
4076
4394
 
4395
+ /**
4396
+ * @private
4397
+ * @inner
4398
+ */
4399
+ function onKeyDown( tracker, event ) {
4400
+ //$.console.log( "keydown %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey );
4401
+ var propagate;
4402
+ if ( tracker.keyDownHandler ) {
4403
+ event = $.getEvent( event );
4404
+ propagate = tracker.keyDownHandler(
4405
+ {
4406
+ eventSource: tracker,
4407
+ keyCode: event.keyCode ? event.keyCode : event.charCode,
4408
+ ctrl: event.ctrlKey,
4409
+ shift: event.shiftKey,
4410
+ alt: event.altKey,
4411
+ meta: event.metaKey,
4412
+ originalEvent: event,
4413
+ preventDefaultAction: false,
4414
+ userData: tracker.userData
4415
+ }
4416
+ );
4417
+ if ( !propagate ) {
4418
+ $.cancelEvent( event );
4419
+ }
4420
+ }
4421
+ }
4422
+
4423
+
4424
+ /**
4425
+ * @private
4426
+ * @inner
4427
+ */
4428
+ function onKeyUp( tracker, event ) {
4429
+ //$.console.log( "keyup %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey );
4430
+ var propagate;
4431
+ if ( tracker.keyUpHandler ) {
4432
+ event = $.getEvent( event );
4433
+ propagate = tracker.keyUpHandler(
4434
+ {
4435
+ eventSource: tracker,
4436
+ keyCode: event.keyCode ? event.keyCode : event.charCode,
4437
+ ctrl: event.ctrlKey,
4438
+ shift: event.shiftKey,
4439
+ alt: event.altKey,
4440
+ meta: event.metaKey,
4441
+ originalEvent: event,
4442
+ preventDefaultAction: false,
4443
+ userData: tracker.userData
4444
+ }
4445
+ );
4446
+ if ( !propagate ) {
4447
+ $.cancelEvent( event );
4448
+ }
4449
+ }
4450
+ }
4451
+
4452
+
4077
4453
  /**
4078
4454
  * @private
4079
4455
  * @inner
4080
4456
  */
4081
4457
  function onKeyPress( tracker, event ) {
4082
- //console.log( "keypress %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey );
4458
+ //$.console.log( "keypress %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey );
4083
4459
  var propagate;
4084
4460
  if ( tracker.keyHandler ) {
4085
4461
  event = $.getEvent( event );
4086
4462
  propagate = tracker.keyHandler(
4087
4463
  {
4088
4464
  eventSource: tracker,
4089
- position: getMouseRelative( event, tracker.element ),
4090
4465
  keyCode: event.keyCode ? event.keyCode : event.charCode,
4466
+ ctrl: event.ctrlKey,
4091
4467
  shift: event.shiftKey,
4468
+ alt: event.altKey,
4469
+ meta: event.metaKey,
4092
4470
  originalEvent: event,
4093
4471
  preventDefaultAction: false,
4094
4472
  userData: tracker.userData
@@ -4250,27 +4628,15 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4250
4628
 
4251
4629
 
4252
4630
  /**
4631
+ * Only used on IE 8
4632
+ *
4253
4633
  * @private
4254
4634
  * @inner
4255
4635
  */
4256
- function onMouseOver( tracker, event ) {
4257
- var gPoint;
4258
-
4636
+ function onMouseEnter( tracker, event ) {
4259
4637
  event = $.getEvent( event );
4260
4638
 
4261
- if ( this === event.relatedTarget || isParentChild( this, event.relatedTarget ) ) {
4262
- return;
4263
- }
4264
-
4265
- gPoint = {
4266
- id: $.MouseTracker.mousePointerId,
4267
- type: 'mouse',
4268
- isPrimary: true,
4269
- currentPos: getMouseAbsolute( event ),
4270
- currentTime: $.now()
4271
- };
4272
-
4273
- updatePointersEnter( tracker, event, [ gPoint ] );
4639
+ handleMouseEnter( tracker, event );
4274
4640
  }
4275
4641
 
4276
4642
 
@@ -4278,24 +4644,14 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4278
4644
  * @private
4279
4645
  * @inner
4280
4646
  */
4281
- function onMouseOut( tracker, event ) {
4282
- var gPoint;
4283
-
4647
+ function onMouseOver( tracker, event ) {
4284
4648
  event = $.getEvent( event );
4285
4649
 
4286
- if ( this === event.relatedTarget || isParentChild( this, event.relatedTarget ) ) {
4650
+ if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) {
4287
4651
  return;
4288
4652
  }
4289
4653
 
4290
- gPoint = {
4291
- id: $.MouseTracker.mousePointerId,
4292
- type: 'mouse',
4293
- isPrimary: true,
4294
- currentPos: getMouseAbsolute( event ),
4295
- currentTime: $.now()
4296
- };
4297
-
4298
- updatePointersExit( tracker, event, [ gPoint ] );
4654
+ handleMouseEnter( tracker, event );
4299
4655
  }
4300
4656
 
4301
4657
 
@@ -4303,12 +4659,8 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4303
4659
  * @private
4304
4660
  * @inner
4305
4661
  */
4306
- function onMouseEnter( tracker, event ) {
4307
- var gPoint;
4308
-
4309
- event = $.getEvent( event );
4310
-
4311
- gPoint = {
4662
+ function handleMouseEnter( tracker, event ) {
4663
+ var gPoint = {
4312
4664
  id: $.MouseTracker.mousePointerId,
4313
4665
  type: 'mouse',
4314
4666
  isPrimary: true,
@@ -4321,15 +4673,39 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4321
4673
 
4322
4674
 
4323
4675
  /**
4676
+ * Only used on IE 8
4677
+ *
4324
4678
  * @private
4325
4679
  * @inner
4326
4680
  */
4327
4681
  function onMouseLeave( tracker, event ) {
4328
- var gPoint;
4682
+ event = $.getEvent( event );
4683
+
4684
+ handleMouseExit( tracker, event );
4685
+ }
4686
+
4329
4687
 
4688
+ /**
4689
+ * @private
4690
+ * @inner
4691
+ */
4692
+ function onMouseOut( tracker, event ) {
4330
4693
  event = $.getEvent( event );
4331
4694
 
4332
- gPoint = {
4695
+ if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) {
4696
+ return;
4697
+ }
4698
+
4699
+ handleMouseExit( tracker, event );
4700
+ }
4701
+
4702
+
4703
+ /**
4704
+ * @private
4705
+ * @inner
4706
+ */
4707
+ function handleMouseExit( tracker, event ) {
4708
+ var gPoint = {
4333
4709
  id: $.MouseTracker.mousePointerId,
4334
4710
  type: 'mouse',
4335
4711
  isPrimary: true,
@@ -4341,6 +4717,31 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4341
4717
  }
4342
4718
 
4343
4719
 
4720
+ /**
4721
+ * Returns a W3C DOM level 3 standard button value given an event.button property:
4722
+ * -1 == none, 0 == primary/left, 1 == middle, 2 == secondary/right, 3 == X1/back, 4 == X2/forward, 5 == eraser (pen)
4723
+ * @private
4724
+ * @inner
4725
+ */
4726
+ function getStandardizedButton( button ) {
4727
+ if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
4728
+ // On IE 8, 0 == none, 1 == left, 2 == right, 3 == left and right, 4 == middle, 5 == left and middle, 6 == right and middle, 7 == all three
4729
+ // TODO: Support chorded (multiple) button presses on IE 8?
4730
+ if ( button === 1 ) {
4731
+ return 0;
4732
+ } else if ( button === 2 ) {
4733
+ return 2;
4734
+ } else if ( button === 4 ) {
4735
+ return 1;
4736
+ } else {
4737
+ return -1;
4738
+ }
4739
+ } else {
4740
+ return button;
4741
+ }
4742
+ }
4743
+
4744
+
4344
4745
  /**
4345
4746
  * @private
4346
4747
  * @inner
@@ -4358,9 +4759,9 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4358
4759
  currentTime: $.now()
4359
4760
  };
4360
4761
 
4361
- if ( updatePointersDown( tracker, event, [ gPoint ], event.button ) ) {
4762
+ if ( updatePointersDown( tracker, event, [ gPoint ], getStandardizedButton( event.button ) ) ) {
4362
4763
  $.stopEvent( event );
4363
- captureMouse( tracker );
4764
+ capturePointer( tracker, 'mouse' );
4364
4765
  }
4365
4766
 
4366
4767
  if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler ) {
@@ -4379,8 +4780,6 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4379
4780
 
4380
4781
  /**
4381
4782
  * This handler is attached to the window object (on the capture phase) to emulate mouse capture.
4382
- * Only triggered in W3C browsers that don't have setCapture/releaseCapture
4383
- * methods or don't support the new pointer events model.
4384
4783
  * onMouseUp is still attached to the tracked element, so stop propagation to avoid processing twice.
4385
4784
  *
4386
4785
  * @private
@@ -4409,8 +4808,8 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4409
4808
  currentTime: $.now()
4410
4809
  };
4411
4810
 
4412
- if ( updatePointersUp( tracker, event, [ gPoint ], event.button ) ) {
4413
- releaseMouse( tracker );
4811
+ if ( updatePointersUp( tracker, event, [ gPoint ], getStandardizedButton( event.button ) ) ) {
4812
+ releasePointer( tracker, 'mouse' );
4414
4813
  }
4415
4814
  }
4416
4815
 
@@ -4426,8 +4825,6 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4426
4825
 
4427
4826
  /**
4428
4827
  * This handler is attached to the window object (on the capture phase) to emulate mouse capture.
4429
- * Only triggered in W3C browsers that don't have setCapture/releaseCapture
4430
- * methods or don't support the new pointer events model.
4431
4828
  * onMouseMove is still attached to the tracked element, so stop propagation to avoid processing twice.
4432
4829
  *
4433
4830
  * @private
@@ -4464,45 +4861,24 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4464
4861
  * @private
4465
4862
  * @inner
4466
4863
  */
4467
- function onTouchEnter( tracker, event ) {
4864
+ function abortTouchContacts( tracker, event, pointsList ) {
4468
4865
  var i,
4469
- touchCount = event.changedTouches.length,
4470
- gPoints = [];
4866
+ gPointCount = pointsList.getLength(),
4867
+ abortGPoints = [];
4471
4868
 
4472
- for ( i = 0; i < touchCount; i++ ) {
4473
- gPoints.push( {
4474
- id: event.changedTouches[ i ].identifier,
4475
- type: 'touch',
4476
- // isPrimary not set - let the updatePointers functions determine it
4477
- currentPos: getMouseAbsolute( event.changedTouches[ i ] ),
4478
- currentTime: $.now()
4479
- } );
4869
+ for ( i = 0; i < gPointCount; i++ ) {
4870
+ abortGPoints.push( pointsList.getByIndex( i ) );
4480
4871
  }
4481
4872
 
4482
- updatePointersEnter( tracker, event, gPoints );
4483
- }
4484
-
4485
-
4486
- /**
4487
- * @private
4488
- * @inner
4489
- */
4490
- function onTouchLeave( tracker, event ) {
4491
- var i,
4492
- touchCount = event.changedTouches.length,
4493
- gPoints = [];
4494
-
4495
- for ( i = 0; i < touchCount; i++ ) {
4496
- gPoints.push( {
4497
- id: event.changedTouches[ i ].identifier,
4498
- type: 'touch',
4499
- // isPrimary not set - let the updatePointers functions determine it
4500
- currentPos: getMouseAbsolute( event.changedTouches[ i ] ),
4501
- currentTime: $.now()
4502
- } );
4873
+ if ( abortGPoints.length > 0 ) {
4874
+ // simulate touchend
4875
+ updatePointersUp( tracker, event, abortGPoints, 0 ); // 0 means primary button press/release or touch contact
4876
+ // release pointer capture
4877
+ pointsList.captureCount = 1;
4878
+ releasePointer( tracker, 'touch' );
4879
+ // simulate touchleave
4880
+ updatePointersExit( tracker, event, abortGPoints );
4503
4881
  }
4504
-
4505
- updatePointersExit( tracker, event, gPoints );
4506
4882
  }
4507
4883
 
4508
4884
 
@@ -4513,11 +4889,19 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4513
4889
  function onTouchStart( tracker, event ) {
4514
4890
  var time,
4515
4891
  i,
4892
+ j,
4516
4893
  touchCount = event.changedTouches.length,
4517
- gPoints = [];
4894
+ gPoints = [],
4895
+ parentGPoints,
4896
+ pointsList = tracker.getActivePointersListByType( 'touch' );
4518
4897
 
4519
4898
  time = $.now();
4520
4899
 
4900
+ if ( pointsList.getLength() > event.touches.length - touchCount ) {
4901
+ $.console.warn('Tracked touch contact count doesn\'t match event.touches.length. Removing all tracked touch pointers.');
4902
+ abortTouchContacts( tracker, event, pointsList );
4903
+ }
4904
+
4521
4905
  for ( i = 0; i < touchCount; i++ ) {
4522
4906
  gPoints.push( {
4523
4907
  id: event.changedTouches[ i ].identifier,
@@ -4528,13 +4912,29 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4528
4912
  } );
4529
4913
  }
4530
4914
 
4531
- // simulate touchenter if not natively available
4532
- if ( !$.MouseTracker.haveTouchEnter ) {
4533
- updatePointersEnter( tracker, event, gPoints );
4915
+ // simulate touchenter on our tracked element
4916
+ updatePointersEnter( tracker, event, gPoints );
4917
+
4918
+ // simulate touchenter on our tracked element's tracked ancestor elements
4919
+ for ( i = 0; i < MOUSETRACKERS.length; i++ ) {
4920
+ if ( MOUSETRACKERS[ i ] !== tracker && MOUSETRACKERS[ i ].isTracking() && isParentChild( MOUSETRACKERS[ i ].element, tracker.element ) ) {
4921
+ parentGPoints = [];
4922
+ for ( j = 0; j < touchCount; j++ ) {
4923
+ parentGPoints.push( {
4924
+ id: event.changedTouches[ j ].identifier,
4925
+ type: 'touch',
4926
+ // isPrimary not set - let the updatePointers functions determine it
4927
+ currentPos: getMouseAbsolute( event.changedTouches[ j ] ),
4928
+ currentTime: time
4929
+ } );
4930
+ }
4931
+ updatePointersEnter( MOUSETRACKERS[ i ], event, parentGPoints );
4932
+ }
4534
4933
  }
4535
4934
 
4536
4935
  if ( updatePointersDown( tracker, event, gPoints, 0 ) ) { // 0 means primary button press/release or touch contact
4537
- // Touch event model start, end, and move events are always captured so we don't need to capture explicitly
4936
+ $.stopEvent( event );
4937
+ capturePointer( tracker, 'touch' );
4538
4938
  }
4539
4939
 
4540
4940
  $.cancelEvent( event );
@@ -4546,10 +4946,34 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4546
4946
  * @inner
4547
4947
  */
4548
4948
  function onTouchEnd( tracker, event ) {
4949
+ handleTouchEnd( tracker, event );
4950
+ }
4951
+
4952
+
4953
+ /**
4954
+ * This handler is attached to the window object (on the capture phase) to emulate pointer capture.
4955
+ * onTouchEnd is still attached to the tracked element, so stop propagation to avoid processing twice.
4956
+ *
4957
+ * @private
4958
+ * @inner
4959
+ */
4960
+ function onTouchEndCaptured( tracker, event ) {
4961
+ handleTouchEnd( tracker, event );
4962
+ $.stopEvent( event );
4963
+ }
4964
+
4965
+
4966
+ /**
4967
+ * @private
4968
+ * @inner
4969
+ */
4970
+ function handleTouchEnd( tracker, event ) {
4549
4971
  var time,
4550
4972
  i,
4973
+ j,
4551
4974
  touchCount = event.changedTouches.length,
4552
- gPoints = [];
4975
+ gPoints = [],
4976
+ parentGPoints;
4553
4977
 
4554
4978
  time = $.now();
4555
4979
 
@@ -4563,13 +4987,28 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4563
4987
  } );
4564
4988
  }
4565
4989
 
4566
- // Touch event model start, end, and move events are always captured so we don't need to release capture.
4567
- // We'll ignore the should-release-capture return value here
4568
- updatePointersUp( tracker, event, gPoints, 0 ); // 0 means primary button press/release or touch contact
4990
+ if ( updatePointersUp( tracker, event, gPoints, 0 ) ) {
4991
+ releasePointer( tracker, 'touch' );
4992
+ }
4993
+
4994
+ // simulate touchleave on our tracked element
4995
+ updatePointersExit( tracker, event, gPoints );
4569
4996
 
4570
- // simulate touchleave if not natively available
4571
- if ( !$.MouseTracker.haveTouchEnter && touchCount > 0 ) {
4572
- updatePointersExit( tracker, event, gPoints );
4997
+ // simulate touchleave on our tracked element's tracked ancestor elements
4998
+ for ( i = 0; i < MOUSETRACKERS.length; i++ ) {
4999
+ if ( MOUSETRACKERS[ i ] !== tracker && MOUSETRACKERS[ i ].isTracking() && isParentChild( MOUSETRACKERS[ i ].element, tracker.element ) ) {
5000
+ parentGPoints = [];
5001
+ for ( j = 0; j < touchCount; j++ ) {
5002
+ parentGPoints.push( {
5003
+ id: event.changedTouches[ j ].identifier,
5004
+ type: 'touch',
5005
+ // isPrimary not set - let the updatePointers functions determine it
5006
+ currentPos: getMouseAbsolute( event.changedTouches[ j ] ),
5007
+ currentTime: time
5008
+ } );
5009
+ }
5010
+ updatePointersExit( MOUSETRACKERS[ i ], event, parentGPoints );
5011
+ }
4573
5012
  }
4574
5013
 
4575
5014
  $.cancelEvent( event );
@@ -4581,6 +5020,28 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4581
5020
  * @inner
4582
5021
  */
4583
5022
  function onTouchMove( tracker, event ) {
5023
+ handleTouchMove( tracker, event );
5024
+ }
5025
+
5026
+
5027
+ /**
5028
+ * This handler is attached to the window object (on the capture phase) to emulate pointer capture.
5029
+ * onTouchMove is still attached to the tracked element, so stop propagation to avoid processing twice.
5030
+ *
5031
+ * @private
5032
+ * @inner
5033
+ */
5034
+ function onTouchMoveCaptured( tracker, event ) {
5035
+ handleTouchMove( tracker, event );
5036
+ $.stopEvent( event );
5037
+ }
5038
+
5039
+
5040
+ /**
5041
+ * @private
5042
+ * @inner
5043
+ */
5044
+ function handleTouchMove( tracker, event ) {
4584
5045
  var i,
4585
5046
  touchCount = event.changedTouches.length,
4586
5047
  gPoints = [];
@@ -4613,7 +5074,7 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4613
5074
  for ( i = 0; i < touchCount; i++ ) {
4614
5075
  gPoints.push( {
4615
5076
  id: event.changedTouches[ i ].identifier,
4616
- type: 'touch',
5077
+ type: 'touch'
4617
5078
  } );
4618
5079
  }
4619
5080
 
@@ -4647,9 +5108,13 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4647
5108
  * @private
4648
5109
  * @inner
4649
5110
  */
4650
- function onPointerEnter( tracker, event ) {
5111
+ function onPointerOver( tracker, event ) {
4651
5112
  var gPoint;
4652
5113
 
5114
+ if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) {
5115
+ return;
5116
+ }
5117
+
4653
5118
  gPoint = {
4654
5119
  id: event.pointerId,
4655
5120
  type: getPointerType( event ),
@@ -4666,9 +5131,13 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4666
5131
  * @private
4667
5132
  * @inner
4668
5133
  */
4669
- function onPointerLeave( tracker, event ) {
5134
+ function onPointerOut( tracker, event ) {
4670
5135
  var gPoint;
4671
5136
 
5137
+ if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) {
5138
+ return;
5139
+ }
5140
+
4672
5141
  gPoint = {
4673
5142
  id: event.pointerId,
4674
5143
  type: getPointerType( event ),
@@ -4697,12 +5166,8 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4697
5166
  };
4698
5167
 
4699
5168
  if ( updatePointersDown( tracker, event, [ gPoint ], event.button ) ) {
4700
- if ( $.MouseTracker.unprefixedPointerEvents ) {
4701
- event.currentTarget.setPointerCapture( event.pointerId );
4702
- } else {
4703
- event.currentTarget.msSetPointerCapture( event.pointerId );
4704
- }
4705
5169
  $.stopEvent( event );
5170
+ capturePointer( tracker, gPoint.type );
4706
5171
  }
4707
5172
 
4708
5173
  if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
@@ -4716,6 +5181,31 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4716
5181
  * @inner
4717
5182
  */
4718
5183
  function onPointerUp( tracker, event ) {
5184
+ handlePointerUp( tracker, event );
5185
+ }
5186
+
5187
+
5188
+ /**
5189
+ * This handler is attached to the window object (on the capture phase) to emulate mouse capture.
5190
+ * onPointerUp is still attached to the tracked element, so stop propagation to avoid processing twice.
5191
+ *
5192
+ * @private
5193
+ * @inner
5194
+ */
5195
+ function onPointerUpCaptured( tracker, event ) {
5196
+ var pointsList = tracker.getActivePointersListByType( getPointerType( event ) );
5197
+ if ( pointsList.getById( event.pointerId ) ) {
5198
+ handlePointerUp( tracker, event );
5199
+ }
5200
+ $.stopEvent( event );
5201
+ }
5202
+
5203
+
5204
+ /**
5205
+ * @private
5206
+ * @inner
5207
+ */
5208
+ function handlePointerUp( tracker, event ) {
4719
5209
  var gPoint;
4720
5210
 
4721
5211
  gPoint = {
@@ -4727,11 +5217,7 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4727
5217
  };
4728
5218
 
4729
5219
  if ( updatePointersUp( tracker, event, [ gPoint ], event.button ) ) {
4730
- if ( $.MouseTracker.unprefixedPointerEvents ) {
4731
- event.currentTarget.releasePointerCapture( event.pointerId );
4732
- } else {
4733
- event.currentTarget.msReleasePointerCapture( event.pointerId );
4734
- }
5220
+ releasePointer( tracker, gPoint.type );
4735
5221
  }
4736
5222
  }
4737
5223
 
@@ -4741,6 +5227,31 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4741
5227
  * @inner
4742
5228
  */
4743
5229
  function onPointerMove( tracker, event ) {
5230
+ handlePointerMove( tracker, event );
5231
+ }
5232
+
5233
+
5234
+ /**
5235
+ * This handler is attached to the window object (on the capture phase) to emulate mouse capture.
5236
+ * onPointerMove is still attached to the tracked element, so stop propagation to avoid processing twice.
5237
+ *
5238
+ * @private
5239
+ * @inner
5240
+ */
5241
+ function onPointerMoveCaptured( tracker, event ) {
5242
+ var pointsList = tracker.getActivePointersListByType( getPointerType( event ) );
5243
+ if ( pointsList.getById( event.pointerId ) ) {
5244
+ handlePointerMove( tracker, event );
5245
+ }
5246
+ $.stopEvent( event );
5247
+ }
5248
+
5249
+
5250
+ /**
5251
+ * @private
5252
+ * @inner
5253
+ */
5254
+ function handlePointerMove( tracker, event ) {
4744
5255
  // Pointer changed coordinates, button state, pressure, tilt, or contact geometry (e.g. width and height)
4745
5256
  var gPoint;
4746
5257
 
@@ -4765,7 +5276,7 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4765
5276
 
4766
5277
  gPoint = {
4767
5278
  id: event.pointerId,
4768
- type: getPointerType( event ),
5279
+ type: getPointerType( event )
4769
5280
  };
4770
5281
 
4771
5282
  updatePointersCancel( tracker, event, [ gPoint ] );
@@ -4891,6 +5402,7 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4891
5402
  pointerType: curGPoint.type,
4892
5403
  position: getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ),
4893
5404
  buttons: pointsList.buttons,
5405
+ pointers: tracker.getActivePointerCount(),
4894
5406
  insideElementPressed: curGPoint.insideElementPressed,
4895
5407
  buttonDownAny: pointsList.buttons !== 0,
4896
5408
  isTouchEvent: curGPoint.type === 'touch',
@@ -4954,6 +5466,7 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4954
5466
  pointerType: curGPoint.type,
4955
5467
  position: getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ),
4956
5468
  buttons: pointsList.buttons,
5469
+ pointers: tracker.getActivePointerCount(),
4957
5470
  insideElementPressed: updateGPoint ? updateGPoint.insideElementPressed : false,
4958
5471
  buttonDownAny: pointsList.buttons !== 0,
4959
5472
  isTouchEvent: curGPoint.type === 'touch',
@@ -4982,7 +5495,7 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4982
5495
  * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints
4983
5496
  * Gesture points associated with the event.
4984
5497
  * @param {Number} buttonChanged
4985
- * The button involved in the event: -1: none, 0: primary, 1: aux, 2: secondary, 3: X1, 4: X2, 5: pen eraser.
5498
+ * The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
4986
5499
  * Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model,
4987
5500
  * only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events.
4988
5501
  *
@@ -5000,30 +5513,71 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
5000
5513
  if ( typeof event.buttons !== 'undefined' ) {
5001
5514
  pointsList.buttons = event.buttons;
5002
5515
  } else {
5003
- if ( buttonChanged === 0 ) {
5004
- // Primary
5005
- pointsList.buttons |= 1;
5006
- } else if ( buttonChanged === 1 ) {
5007
- // Aux
5008
- pointsList.buttons |= 4;
5009
- } else if ( buttonChanged === 2 ) {
5010
- // Secondary
5011
- pointsList.buttons |= 2;
5012
- } else if ( buttonChanged === 3 ) {
5013
- // X1 (Back)
5014
- pointsList.buttons |= 8;
5015
- } else if ( buttonChanged === 4 ) {
5016
- // X2 (Forward)
5017
- pointsList.buttons |= 16;
5018
- } else if ( buttonChanged === 5 ) {
5019
- // Pen Eraser
5020
- pointsList.buttons |= 32;
5516
+ if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
5517
+ if ( buttonChanged === 0 ) {
5518
+ // Primary
5519
+ pointsList.buttons += 1;
5520
+ } else if ( buttonChanged === 1 ) {
5521
+ // Aux
5522
+ pointsList.buttons += 4;
5523
+ } else if ( buttonChanged === 2 ) {
5524
+ // Secondary
5525
+ pointsList.buttons += 2;
5526
+ } else if ( buttonChanged === 3 ) {
5527
+ // X1 (Back)
5528
+ pointsList.buttons += 8;
5529
+ } else if ( buttonChanged === 4 ) {
5530
+ // X2 (Forward)
5531
+ pointsList.buttons += 16;
5532
+ } else if ( buttonChanged === 5 ) {
5533
+ // Pen Eraser
5534
+ pointsList.buttons += 32;
5535
+ }
5536
+ } else {
5537
+ if ( buttonChanged === 0 ) {
5538
+ // Primary
5539
+ pointsList.buttons |= 1;
5540
+ } else if ( buttonChanged === 1 ) {
5541
+ // Aux
5542
+ pointsList.buttons |= 4;
5543
+ } else if ( buttonChanged === 2 ) {
5544
+ // Secondary
5545
+ pointsList.buttons |= 2;
5546
+ } else if ( buttonChanged === 3 ) {
5547
+ // X1 (Back)
5548
+ pointsList.buttons |= 8;
5549
+ } else if ( buttonChanged === 4 ) {
5550
+ // X2 (Forward)
5551
+ pointsList.buttons |= 16;
5552
+ } else if ( buttonChanged === 5 ) {
5553
+ // Pen Eraser
5554
+ pointsList.buttons |= 32;
5555
+ }
5021
5556
  }
5022
5557
  }
5023
5558
 
5024
5559
  // Only capture and track primary button, pen, and touch contacts
5025
- //if ( buttonChanged !== 0 ) {
5026
- if ( buttonChanged !== 0 && buttonChanged !== 1 ) { //TODO Remove this IE8 compatibility and use the commented line above
5560
+ if ( buttonChanged !== 0 ) {
5561
+ // Aux Press
5562
+ if ( tracker.nonPrimaryPressHandler ) {
5563
+ propagate = tracker.nonPrimaryPressHandler(
5564
+ {
5565
+ eventSource: tracker,
5566
+ pointerType: gPoints[ 0 ].type,
5567
+ position: getPointRelativeToAbsolute( gPoints[ 0 ].currentPos, tracker.element ),
5568
+ button: buttonChanged,
5569
+ buttons: pointsList.buttons,
5570
+ isTouchEvent: gPoints[ 0 ].type === 'touch',
5571
+ originalEvent: event,
5572
+ preventDefaultAction: false,
5573
+ userData: tracker.userData
5574
+ }
5575
+ );
5576
+ if ( propagate === false ) {
5577
+ $.cancelEvent( event );
5578
+ }
5579
+ }
5580
+
5027
5581
  return false;
5028
5582
  }
5029
5583
 
@@ -5053,6 +5607,7 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
5053
5607
  }
5054
5608
 
5055
5609
  pointsList.contacts++;
5610
+ //$.console.log('contacts++ ', pointsList.contacts);
5056
5611
 
5057
5612
  if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
5058
5613
  $.MouseTracker.gesturePointVelocityTracker.addPoint( tracker, curGPoint );
@@ -5102,7 +5657,7 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
5102
5657
  * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints
5103
5658
  * Gesture points associated with the event.
5104
5659
  * @param {Number} buttonChanged
5105
- * The button involved in the event: -1: none, 0: primary, 1: aux, 2: secondary, 3: X1, 4: X2, 5: pen eraser.
5660
+ * The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
5106
5661
  * Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model,
5107
5662
  * only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events.
5108
5663
  *
@@ -5126,30 +5681,71 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
5126
5681
  if ( typeof event.buttons !== 'undefined' ) {
5127
5682
  pointsList.buttons = event.buttons;
5128
5683
  } else {
5129
- if ( buttonChanged === 0 ) {
5130
- // Primary
5131
- pointsList.buttons ^= ~1;
5132
- } else if ( buttonChanged === 1 ) {
5133
- // Aux
5134
- pointsList.buttons ^= ~4;
5135
- } else if ( buttonChanged === 2 ) {
5136
- // Secondary
5137
- pointsList.buttons ^= ~2;
5138
- } else if ( buttonChanged === 3 ) {
5139
- // X1 (Back)
5140
- pointsList.buttons ^= ~8;
5141
- } else if ( buttonChanged === 4 ) {
5142
- // X2 (Forward)
5143
- pointsList.buttons ^= ~16;
5144
- } else if ( buttonChanged === 5 ) {
5145
- // Pen Eraser
5146
- pointsList.buttons ^= ~32;
5684
+ if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
5685
+ if ( buttonChanged === 0 ) {
5686
+ // Primary
5687
+ pointsList.buttons -= 1;
5688
+ } else if ( buttonChanged === 1 ) {
5689
+ // Aux
5690
+ pointsList.buttons -= 4;
5691
+ } else if ( buttonChanged === 2 ) {
5692
+ // Secondary
5693
+ pointsList.buttons -= 2;
5694
+ } else if ( buttonChanged === 3 ) {
5695
+ // X1 (Back)
5696
+ pointsList.buttons -= 8;
5697
+ } else if ( buttonChanged === 4 ) {
5698
+ // X2 (Forward)
5699
+ pointsList.buttons -= 16;
5700
+ } else if ( buttonChanged === 5 ) {
5701
+ // Pen Eraser
5702
+ pointsList.buttons -= 32;
5703
+ }
5704
+ } else {
5705
+ if ( buttonChanged === 0 ) {
5706
+ // Primary
5707
+ pointsList.buttons ^= ~1;
5708
+ } else if ( buttonChanged === 1 ) {
5709
+ // Aux
5710
+ pointsList.buttons ^= ~4;
5711
+ } else if ( buttonChanged === 2 ) {
5712
+ // Secondary
5713
+ pointsList.buttons ^= ~2;
5714
+ } else if ( buttonChanged === 3 ) {
5715
+ // X1 (Back)
5716
+ pointsList.buttons ^= ~8;
5717
+ } else if ( buttonChanged === 4 ) {
5718
+ // X2 (Forward)
5719
+ pointsList.buttons ^= ~16;
5720
+ } else if ( buttonChanged === 5 ) {
5721
+ // Pen Eraser
5722
+ pointsList.buttons ^= ~32;
5723
+ }
5147
5724
  }
5148
5725
  }
5149
5726
 
5150
5727
  // Only capture and track primary button, pen, and touch contacts
5151
- //if ( buttonChanged !== 0 ) {
5152
- if ( buttonChanged !== 0 && buttonChanged !== 1 ) { //TODO Remove this IE8 compatibility and use the commented line above
5728
+ if ( buttonChanged !== 0 ) {
5729
+ // Aux Release
5730
+ if ( tracker.nonPrimaryReleaseHandler ) {
5731
+ propagate = tracker.nonPrimaryReleaseHandler(
5732
+ {
5733
+ eventSource: tracker,
5734
+ pointerType: gPoints[ 0 ].type,
5735
+ position: getPointRelativeToAbsolute( gPoints[ 0 ].currentPos, tracker.element ),
5736
+ button: buttonChanged,
5737
+ buttons: pointsList.buttons,
5738
+ isTouchEvent: gPoints[ 0 ].type === 'touch',
5739
+ originalEvent: event,
5740
+ preventDefaultAction: false,
5741
+ userData: tracker.userData
5742
+ }
5743
+ );
5744
+ if ( propagate === false ) {
5745
+ $.cancelEvent( event );
5746
+ }
5747
+ }
5748
+
5153
5749
  return false;
5154
5750
  }
5155
5751
 
@@ -5179,6 +5775,7 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
5179
5775
  // Pointer was activated in our element but could have been removed in any element since events are captured to our element
5180
5776
 
5181
5777
  pointsList.contacts--;
5778
+ //$.console.log('contacts-- ', pointsList.contacts);
5182
5779
 
5183
5780
  if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
5184
5781
  $.MouseTracker.gesturePointVelocityTracker.removePoint( tracker, updateGPoint );
@@ -6079,14 +6676,6 @@ $.Viewer = function( options ) {
6079
6676
  * @memberof OpenSeadragon.Viewer#
6080
6677
  */
6081
6678
  container: null,
6082
- /**
6083
- * A &lt;textarea&gt; element, the element where keyboard events are handled.<br><br>
6084
- * Child element of {@link OpenSeadragon.Viewer#container},
6085
- * positioned below {@link OpenSeadragon.Viewer#canvas}.
6086
- * @member {Element} keyboardCommandArea
6087
- * @memberof OpenSeadragon.Viewer#
6088
- */
6089
- keyboardCommandArea: null,
6090
6679
  /**
6091
6680
  * A &lt;div&gt; element, the element where user-input events are handled for panning and zooming.<br><br>
6092
6681
  * Child element of {@link OpenSeadragon.Viewer#container},
@@ -6234,7 +6823,6 @@ $.Viewer = function( options ) {
6234
6823
 
6235
6824
  this.element = this.element || document.getElementById( this.id );
6236
6825
  this.canvas = $.makeNeutralElement( "div" );
6237
- this.keyboardCommandArea = $.makeNeutralElement( "textarea" );
6238
6826
  this.drawersContainer = $.makeNeutralElement( "div" );
6239
6827
  this.overlaysContainer = $.makeNeutralElement( "div" );
6240
6828
 
@@ -6246,13 +6834,9 @@ $.Viewer = function( options ) {
6246
6834
  style.position = "absolute";
6247
6835
  style.top = "0px";
6248
6836
  style.left = "0px";
6249
- // Disable browser default touch handling
6250
- if (style["touch-action"] !== undefined) {
6251
- style["touch-action"] = "none";
6252
- } else if (style["-ms-touch-action"] !== undefined) {
6253
- style["-ms-touch-action"] = "none";
6254
- }
6255
6837
  }(this.canvas.style));
6838
+ $.setElementTouchActionNone( this.canvas );
6839
+ this.canvas.tabIndex = options.tabIndex || 0;
6256
6840
 
6257
6841
  //the container is created through applying the ControlDock constructor above
6258
6842
  this.container.className = "openseadragon-container";
@@ -6266,19 +6850,7 @@ $.Viewer = function( options ) {
6266
6850
  style.textAlign = "left"; // needed to protect against
6267
6851
  }( this.container.style ));
6268
6852
 
6269
- this.keyboardCommandArea.className = "keyboard-command-area";
6270
- (function( style ){
6271
- style.width = "100%";
6272
- style.height = "100%";
6273
- style.overflow = "hidden";
6274
- style.position = "absolute";
6275
- style.top = "0px";
6276
- style.left = "0px";
6277
- style.resize = "none";
6278
- }( this.keyboardCommandArea.style ));
6279
-
6280
6853
  this.container.insertBefore( this.canvas, this.container.firstChild );
6281
- this.container.insertBefore( this.keyboardCommandArea, this.container.firstChild );
6282
6854
  this.element.appendChild( this.container );
6283
6855
  this.canvas.appendChild( this.drawersContainer );
6284
6856
  this.canvas.appendChild( this.overlaysContainer );
@@ -6291,96 +6863,39 @@ $.Viewer = function( options ) {
6291
6863
  this.bodyOverflow = document.body.style.overflow;
6292
6864
  this.docOverflow = document.documentElement.style.overflow;
6293
6865
 
6294
- this.keyboardCommandArea.innerTracker = new $.MouseTracker({
6295
- _this : this,
6296
- element: this.keyboardCommandArea,
6297
- focusHandler: function( event ){
6298
- if ( !event.preventDefaultAction ) {
6299
- var point = $.getElementPosition( this.element );
6300
- window.scrollTo( 0, point.y );
6301
- }
6302
- },
6303
-
6304
- keyHandler: function( event ){
6305
- if ( !event.preventDefaultAction ) {
6306
- switch( event.keyCode ){
6307
- case 61://=|+
6308
- _this.viewport.zoomBy(1.1);
6309
- _this.viewport.applyConstraints();
6310
- return false;
6311
- case 45://-|_
6312
- _this.viewport.zoomBy(0.9);
6313
- _this.viewport.applyConstraints();
6314
- return false;
6315
- case 48://0|)
6316
- _this.viewport.goHome();
6317
- _this.viewport.applyConstraints();
6318
- return false;
6319
- case 119://w
6320
- case 87://W
6321
- case 38://up arrow
6322
- if ( event.shift ) {
6323
- _this.viewport.zoomBy(1.1);
6324
- } else {
6325
- _this.viewport.panBy(new $.Point(0, -0.05));
6326
- }
6327
- _this.viewport.applyConstraints();
6328
- return false;
6329
- case 115://s
6330
- case 83://S
6331
- case 40://down arrow
6332
- if ( event.shift ) {
6333
- _this.viewport.zoomBy(0.9);
6334
- } else {
6335
- _this.viewport.panBy(new $.Point(0, 0.05));
6336
- }
6337
- _this.viewport.applyConstraints();
6338
- return false;
6339
- case 97://a
6340
- case 37://left arrow
6341
- _this.viewport.panBy(new $.Point(-0.05, 0));
6342
- _this.viewport.applyConstraints();
6343
- return false;
6344
- case 100://d
6345
- case 39://right arrow
6346
- _this.viewport.panBy(new $.Point(0.05, 0));
6347
- _this.viewport.applyConstraints();
6348
- return false;
6349
- default:
6350
- //console.log( 'navigator keycode %s', event.keyCode );
6351
- return true;
6352
- }
6353
- }
6354
- }
6355
- }).setTracking( true ); // default state
6356
-
6357
-
6358
6866
  this.innerTracker = new $.MouseTracker({
6359
- element: this.canvas,
6360
- clickTimeThreshold: this.clickTimeThreshold,
6361
- clickDistThreshold: this.clickDistThreshold,
6362
- dblClickTimeThreshold: this.dblClickTimeThreshold,
6363
- dblClickDistThreshold: this.dblClickDistThreshold,
6364
- clickHandler: $.delegate( this, onCanvasClick ),
6365
- dblClickHandler: $.delegate( this, onCanvasDblClick ),
6366
- dragHandler: $.delegate( this, onCanvasDrag ),
6367
- dragEndHandler: $.delegate( this, onCanvasDragEnd ),
6368
- releaseHandler: $.delegate( this, onCanvasRelease ),
6369
- scrollHandler: $.delegate( this, onCanvasScroll ),
6370
- pinchHandler: $.delegate( this, onCanvasPinch )
6371
- }).setTracking( this.mouseNavEnabled ? true : false ); // default state
6867
+ element: this.canvas,
6868
+ startDisabled: this.mouseNavEnabled ? false : true,
6869
+ clickTimeThreshold: this.clickTimeThreshold,
6870
+ clickDistThreshold: this.clickDistThreshold,
6871
+ dblClickTimeThreshold: this.dblClickTimeThreshold,
6872
+ dblClickDistThreshold: this.dblClickDistThreshold,
6873
+ keyDownHandler: $.delegate( this, onCanvasKeyDown ),
6874
+ keyHandler: $.delegate( this, onCanvasKeyPress ),
6875
+ clickHandler: $.delegate( this, onCanvasClick ),
6876
+ dblClickHandler: $.delegate( this, onCanvasDblClick ),
6877
+ dragHandler: $.delegate( this, onCanvasDrag ),
6878
+ dragEndHandler: $.delegate( this, onCanvasDragEnd ),
6879
+ enterHandler: $.delegate( this, onCanvasEnter ),
6880
+ exitHandler: $.delegate( this, onCanvasExit ),
6881
+ pressHandler: $.delegate( this, onCanvasPress ),
6882
+ releaseHandler: $.delegate( this, onCanvasRelease ),
6883
+ nonPrimaryPressHandler: $.delegate( this, onCanvasNonPrimaryPress ),
6884
+ nonPrimaryReleaseHandler: $.delegate( this, onCanvasNonPrimaryRelease ),
6885
+ scrollHandler: $.delegate( this, onCanvasScroll ),
6886
+ pinchHandler: $.delegate( this, onCanvasPinch )
6887
+ });
6372
6888
 
6373
6889
  this.outerTracker = new $.MouseTracker({
6374
6890
  element: this.container,
6891
+ startDisabled: this.mouseNavEnabled ? false : true,
6375
6892
  clickTimeThreshold: this.clickTimeThreshold,
6376
6893
  clickDistThreshold: this.clickDistThreshold,
6377
6894
  dblClickTimeThreshold: this.dblClickTimeThreshold,
6378
6895
  dblClickDistThreshold: this.dblClickDistThreshold,
6379
6896
  enterHandler: $.delegate( this, onContainerEnter ),
6380
- exitHandler: $.delegate( this, onContainerExit ),
6381
- pressHandler: $.delegate( this, onContainerPress ),
6382
- releaseHandler: $.delegate( this, onContainerRelease )
6383
- }).setTracking( this.mouseNavEnabled ? true : false ); // always tracking
6897
+ exitHandler: $.delegate( this, onContainerExit )
6898
+ });
6384
6899
 
6385
6900
  if( this.toolbar ){
6386
6901
  this.toolbar = new $.ControlDock({ element: this.toolbar });
@@ -6500,6 +7015,12 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
6500
7015
  * @fires OpenSeadragon.Viewer.event:close
6501
7016
  */
6502
7017
  close: function ( ) {
7018
+
7019
+ if ( !THIS[ this.hash ] ) {
7020
+ //this viewer has already been destroyed: returning immediately
7021
+ return this;
7022
+ }
7023
+
6503
7024
  if ( this._updateRequestId !== null ) {
6504
7025
  $.cancelAnimationFrame( this._updateRequestId );
6505
7026
  this._updateRequestId = null;
@@ -6509,9 +7030,17 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
6509
7030
  this.navigator.close();
6510
7031
  }
6511
7032
 
6512
- this.clearOverlays();
7033
+ if( ! this.preserveOverlays)
7034
+ {
7035
+ this.clearOverlays();
7036
+ this.overlaysContainer.innerHTML = "";
7037
+ }
7038
+
6513
7039
  this.drawersContainer.innerHTML = "";
6514
- this.overlaysContainer.innerHTML = "";
7040
+
7041
+ if ( this.drawer ) {
7042
+ this.drawer.destroy();
7043
+ }
6515
7044
 
6516
7045
  this.source = null;
6517
7046
  this.drawer = null;
@@ -6539,13 +7068,26 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
6539
7068
 
6540
7069
 
6541
7070
  /**
6542
- * Function to destroy the viewer and clean up everything created by
6543
- * OpenSeadragon.
7071
+ * Function to destroy the viewer and clean up everything created by OpenSeadragon.
7072
+ *
7073
+ * Example:
7074
+ * var viewer = OpenSeadragon({
7075
+ * [...]
7076
+ * });
7077
+ *
7078
+ * //when you are done with the viewer:
7079
+ * viewer.destroy();
7080
+ * viewer = null; //important
7081
+ *
6544
7082
  * @function
6545
7083
  */
6546
7084
  destroy: function( ) {
6547
7085
  this.close();
6548
7086
 
7087
+ //TODO: implement this...
7088
+ //this.unbindSequenceControls()
7089
+ //this.unbindStandardControls()
7090
+
6549
7091
  this.removeAllHandlers();
6550
7092
 
6551
7093
  // Go through top element (passed to us) and remove all children
@@ -6558,9 +7100,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
6558
7100
  }
6559
7101
 
6560
7102
  // destroy the mouse trackers
6561
- if (this.keyboardCommandArea){
6562
- this.keyboardCommandArea.innerTracker.destroy();
6563
- }
6564
7103
  if (this.innerTracker){
6565
7104
  this.innerTracker.destroy();
6566
7105
  }
@@ -6568,9 +7107,11 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
6568
7107
  this.outerTracker.destroy();
6569
7108
  }
6570
7109
 
7110
+ THIS[ this.hash ] = null;
7111
+ delete THIS[ this.hash ];
7112
+
6571
7113
  // clear all our references to dom objects
6572
7114
  this.canvas = null;
6573
- this.keyboardCommandArea = null;
6574
7115
  this.container = null;
6575
7116
 
6576
7117
  // clear our reference to the main element - they will need to pass it in again, creating a new viewer
@@ -7887,7 +8428,8 @@ function openTileSource( viewer, source ) {
7887
8428
  degrees: _this.degrees //,
7888
8429
  //TODO: figure out how to support these in a way that makes sense
7889
8430
  //minZoomLevel: this.minZoomLevel,
7890
- //maxZoomLevel: this.maxZoomLevel
8431
+ //maxZoomLevel: this.maxZoomLevel,
8432
+ //homeFillsViewer: this.homeFillsViewer
7891
8433
  });
7892
8434
  } else {
7893
8435
  if( source ){
@@ -7907,7 +8449,9 @@ function openTileSource( viewer, source ) {
7907
8449
  minZoomLevel: _this.minZoomLevel,
7908
8450
  maxZoomLevel: _this.maxZoomLevel,
7909
8451
  viewer: _this,
7910
- degrees: _this.degrees
8452
+ degrees: _this.degrees,
8453
+ navigatorRotate: _this.navigatorRotate,
8454
+ homeFillsViewer: _this.homeFillsViewer
7911
8455
  });
7912
8456
  }
7913
8457
 
@@ -7915,6 +8459,10 @@ function openTileSource( viewer, source ) {
7915
8459
  _this.viewport.resetContentSize( _this.source.dimensions );
7916
8460
  }
7917
8461
 
8462
+ if( _this.preserveOverlays ){
8463
+ _this.overlays = _this.currentOverlays;
8464
+ }
8465
+
7918
8466
  _this.source.overlays = _this.source.overlays || [];
7919
8467
 
7920
8468
  _this.drawer = new $.Drawer({
@@ -7974,7 +8522,8 @@ function openTileSource( viewer, source ) {
7974
8522
  tileSources: source,
7975
8523
  tileHost: _this.tileHost,
7976
8524
  prefixUrl: _this.prefixUrl,
7977
- viewer: _this
8525
+ viewer: _this,
8526
+ navigatorRotate: _this.navigatorRotate
7978
8527
  });
7979
8528
  }
7980
8529
  }
@@ -8208,9 +8757,102 @@ function onBlur(){
8208
8757
 
8209
8758
  }
8210
8759
 
8211
- function onCanvasClick( event ) {
8212
- var gestureSettings;
8213
-
8760
+ function onCanvasKeyDown( event ) {
8761
+ if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
8762
+ switch( event.keyCode ){
8763
+ case 38://up arrow
8764
+ if ( event.shift ) {
8765
+ this.viewport.zoomBy(1.1);
8766
+ } else {
8767
+ this.viewport.panBy(new $.Point(0, -0.05));
8768
+ }
8769
+ this.viewport.applyConstraints();
8770
+ return false;
8771
+ case 40://down arrow
8772
+ if ( event.shift ) {
8773
+ this.viewport.zoomBy(0.9);
8774
+ } else {
8775
+ this.viewport.panBy(new $.Point(0, 0.05));
8776
+ }
8777
+ this.viewport.applyConstraints();
8778
+ return false;
8779
+ case 37://left arrow
8780
+ this.viewport.panBy(new $.Point(-0.05, 0));
8781
+ this.viewport.applyConstraints();
8782
+ return false;
8783
+ case 39://right arrow
8784
+ this.viewport.panBy(new $.Point(0.05, 0));
8785
+ this.viewport.applyConstraints();
8786
+ return false;
8787
+ default:
8788
+ //console.log( 'navigator keycode %s', event.keyCode );
8789
+ return true;
8790
+ }
8791
+ } else {
8792
+ return true;
8793
+ }
8794
+ }
8795
+
8796
+ function onCanvasKeyPress( event ) {
8797
+ if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
8798
+ switch( event.keyCode ){
8799
+ case 61://=|+
8800
+ this.viewport.zoomBy(1.1);
8801
+ this.viewport.applyConstraints();
8802
+ return false;
8803
+ case 45://-|_
8804
+ this.viewport.zoomBy(0.9);
8805
+ this.viewport.applyConstraints();
8806
+ return false;
8807
+ case 48://0|)
8808
+ this.viewport.goHome();
8809
+ this.viewport.applyConstraints();
8810
+ return false;
8811
+ case 119://w
8812
+ case 87://W
8813
+ if ( event.shift ) {
8814
+ this.viewport.zoomBy(1.1);
8815
+ } else {
8816
+ this.viewport.panBy(new $.Point(0, -0.05));
8817
+ }
8818
+ this.viewport.applyConstraints();
8819
+ return false;
8820
+ case 115://s
8821
+ case 83://S
8822
+ if ( event.shift ) {
8823
+ this.viewport.zoomBy(0.9);
8824
+ } else {
8825
+ this.viewport.panBy(new $.Point(0, 0.05));
8826
+ }
8827
+ this.viewport.applyConstraints();
8828
+ return false;
8829
+ case 97://a
8830
+ this.viewport.panBy(new $.Point(-0.05, 0));
8831
+ this.viewport.applyConstraints();
8832
+ return false;
8833
+ case 100://d
8834
+ this.viewport.panBy(new $.Point(0.05, 0));
8835
+ this.viewport.applyConstraints();
8836
+ return false;
8837
+ default:
8838
+ //console.log( 'navigator keycode %s', event.keyCode );
8839
+ return true;
8840
+ }
8841
+ } else {
8842
+ return true;
8843
+ }
8844
+ }
8845
+
8846
+ function onCanvasClick( event ) {
8847
+ var gestureSettings;
8848
+
8849
+ var haveKeyboardFocus = document.activeElement == this.canvas;
8850
+
8851
+ // If we don't have keyboard focus, request it.
8852
+ if ( !haveKeyboardFocus ) {
8853
+ this.canvas.focus();
8854
+ }
8855
+
8214
8856
  if ( !event.preventDefaultAction && this.viewport && event.quick ) {
8215
8857
  gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
8216
8858
  if ( gestureSettings.clickToZoom ) {
@@ -8327,8 +8969,8 @@ function onCanvasDragEnd( event ) {
8327
8969
  if ( !event.preventDefaultAction && this.viewport ) {
8328
8970
  gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
8329
8971
  if ( gestureSettings.flickEnabled && event.speed >= gestureSettings.flickMinSpeed ) {
8330
- var amplitudeX = gestureSettings.flickMomentum * ( event.speed * Math.cos( event.direction ) ),
8331
- amplitudeY = gestureSettings.flickMomentum * ( event.speed * Math.sin( event.direction ) ),
8972
+ var amplitudeX = gestureSettings.flickMomentum * ( event.speed * Math.cos( event.direction - (Math.PI / 180 * this.viewport.degrees) ) ),
8973
+ amplitudeY = gestureSettings.flickMomentum * ( event.speed * Math.sin( event.direction - (Math.PI / 180 * this.viewport.degrees) ) ),
8332
8974
  center = this.viewport.pixelFromPoint( this.viewport.getCenter( true ) ),
8333
8975
  target = this.viewport.pointFromPixel( new $.Point( center.x - amplitudeX, center.y - amplitudeY ) );
8334
8976
  if( !this.panHorizontal ) {
@@ -8338,8 +8980,8 @@ function onCanvasDragEnd( event ) {
8338
8980
  target.y = center.y;
8339
8981
  }
8340
8982
  this.viewport.panTo( target, false );
8341
- this.viewport.applyConstraints();
8342
8983
  }
8984
+ this.viewport.applyConstraints();
8343
8985
  }
8344
8986
  /**
8345
8987
  * Raised when a mouse or touch drag operation ends on the {@link OpenSeadragon.Viewer#canvas} element.
@@ -8366,24 +9008,102 @@ function onCanvasDragEnd( event ) {
8366
9008
  });
8367
9009
  }
8368
9010
 
8369
- function onCanvasRelease( event ) {
8370
- var gestureSettings;
9011
+ function onCanvasEnter( event ) {
9012
+ /**
9013
+ * Raised when a pointer enters the {@link OpenSeadragon.Viewer#canvas} element.
9014
+ *
9015
+ * @event canvas-enter
9016
+ * @memberof OpenSeadragon.Viewer
9017
+ * @type {object}
9018
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
9019
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
9020
+ * @property {String} pointerType - "mouse", "touch", "pen", etc.
9021
+ * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
9022
+ * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
9023
+ * @property {Number} pointers - Number of pointers (all types) active in the tracked element.
9024
+ * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
9025
+ * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
9026
+ * @property {Object} originalEvent - The original DOM event.
9027
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
9028
+ */
9029
+ this.raiseEvent( 'canvas-enter', {
9030
+ tracker: event.eventSource,
9031
+ pointerType: event.pointerType,
9032
+ position: event.position,
9033
+ buttons: event.buttons,
9034
+ pointers: event.pointers,
9035
+ insideElementPressed: event.insideElementPressed,
9036
+ buttonDownAny: event.buttonDownAny,
9037
+ originalEvent: event.originalEvent
9038
+ });
9039
+ }
8371
9040
 
8372
- if ( event.insideElementPressed && this.viewport ) {
8373
- gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
9041
+ function onCanvasExit( event ) {
9042
+ /**
9043
+ * Raised when a pointer leaves the {@link OpenSeadragon.Viewer#canvas} element.
9044
+ *
9045
+ * @event canvas-exit
9046
+ * @memberof OpenSeadragon.Viewer
9047
+ * @type {object}
9048
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
9049
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
9050
+ * @property {String} pointerType - "mouse", "touch", "pen", etc.
9051
+ * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
9052
+ * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
9053
+ * @property {Number} pointers - Number of pointers (all types) active in the tracked element.
9054
+ * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
9055
+ * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
9056
+ * @property {Object} originalEvent - The original DOM event.
9057
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
9058
+ */
9059
+ this.raiseEvent( 'canvas-exit', {
9060
+ tracker: event.eventSource,
9061
+ pointerType: event.pointerType,
9062
+ position: event.position,
9063
+ buttons: event.buttons,
9064
+ pointers: event.pointers,
9065
+ insideElementPressed: event.insideElementPressed,
9066
+ buttonDownAny: event.buttonDownAny,
9067
+ originalEvent: event.originalEvent
9068
+ });
9069
+ }
8374
9070
 
8375
- if ( !gestureSettings.flickEnabled ) {
8376
- this.viewport.applyConstraints();
8377
- }
8378
- }
9071
+ function onCanvasPress( event ) {
8379
9072
  /**
8380
- * Raised when the mouse button is released or touch ends on the {@link OpenSeadragon.Viewer#canvas} element.
9073
+ * Raised when the primary mouse button is pressed or touch starts on the {@link OpenSeadragon.Viewer#canvas} element.
9074
+ *
9075
+ * @event canvas-press
9076
+ * @memberof OpenSeadragon.Viewer
9077
+ * @type {object}
9078
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
9079
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
9080
+ * @property {String} pointerType - "mouse", "touch", "pen", etc.
9081
+ * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
9082
+ * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
9083
+ * @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released.
9084
+ * @property {Object} originalEvent - The original DOM event.
9085
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
9086
+ */
9087
+ this.raiseEvent( 'canvas-press', {
9088
+ tracker: event.eventSource,
9089
+ pointerType: event.pointerType,
9090
+ position: event.position,
9091
+ insideElementPressed: event.insideElementPressed,
9092
+ insideElementReleased: event.insideElementReleased,
9093
+ originalEvent: event.originalEvent
9094
+ });
9095
+ }
9096
+
9097
+ function onCanvasRelease( event ) {
9098
+ /**
9099
+ * Raised when the primary mouse button is released or touch ends on the {@link OpenSeadragon.Viewer#canvas} element.
8381
9100
  *
8382
9101
  * @event canvas-release
8383
9102
  * @memberof OpenSeadragon.Viewer
8384
9103
  * @type {object}
8385
9104
  * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
8386
9105
  * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
9106
+ * @property {String} pointerType - "mouse", "touch", "pen", etc.
8387
9107
  * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
8388
9108
  * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
8389
9109
  * @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released.
@@ -8392,6 +9112,7 @@ function onCanvasRelease( event ) {
8392
9112
  */
8393
9113
  this.raiseEvent( 'canvas-release', {
8394
9114
  tracker: event.eventSource,
9115
+ pointerType: event.pointerType,
8395
9116
  position: event.position,
8396
9117
  insideElementPressed: event.insideElementPressed,
8397
9118
  insideElementReleased: event.insideElementReleased,
@@ -8399,6 +9120,62 @@ function onCanvasRelease( event ) {
8399
9120
  });
8400
9121
  }
8401
9122
 
9123
+ function onCanvasNonPrimaryPress( event ) {
9124
+ /**
9125
+ * Raised when any non-primary pointer button is pressed on the {@link OpenSeadragon.Viewer#canvas} element.
9126
+ *
9127
+ * @event canvas-nonprimary-press
9128
+ * @memberof OpenSeadragon.Viewer
9129
+ * @type {object}
9130
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
9131
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
9132
+ * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
9133
+ * @property {String} pointerType - "mouse", "touch", "pen", etc.
9134
+ * @property {Number} button - Button which caused the event.
9135
+ * -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
9136
+ * @property {Number} buttons - Current buttons pressed.
9137
+ * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
9138
+ * @property {Object} originalEvent - The original DOM event.
9139
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
9140
+ */
9141
+ this.raiseEvent( 'canvas-nonprimary-press', {
9142
+ tracker: event.eventSource,
9143
+ position: event.position,
9144
+ pointerType: event.pointerType,
9145
+ button: event.button,
9146
+ buttons: event.buttons,
9147
+ originalEvent: event.originalEvent
9148
+ });
9149
+ }
9150
+
9151
+ function onCanvasNonPrimaryRelease( event ) {
9152
+ /**
9153
+ * Raised when any non-primary pointer button is released on the {@link OpenSeadragon.Viewer#canvas} element.
9154
+ *
9155
+ * @event canvas-nonprimary-release
9156
+ * @memberof OpenSeadragon.Viewer
9157
+ * @type {object}
9158
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
9159
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
9160
+ * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
9161
+ * @property {String} pointerType - "mouse", "touch", "pen", etc.
9162
+ * @property {Number} button - Button which caused the event.
9163
+ * -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
9164
+ * @property {Number} buttons - Current buttons pressed.
9165
+ * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
9166
+ * @property {Object} originalEvent - The original DOM event.
9167
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
9168
+ */
9169
+ this.raiseEvent( 'canvas-nonprimary-release', {
9170
+ tracker: event.eventSource,
9171
+ position: event.position,
9172
+ pointerType: event.pointerType,
9173
+ button: event.button,
9174
+ buttons: event.buttons,
9175
+ originalEvent: event.originalEvent
9176
+ });
9177
+ }
9178
+
8402
9179
  function onCanvasPinch( event ) {
8403
9180
  var gestureSettings,
8404
9181
  centerPt,
@@ -8421,6 +9198,14 @@ function onCanvasPinch( event ) {
8421
9198
  this.viewport.panBy( panByPt, true );
8422
9199
  this.viewport.applyConstraints();
8423
9200
  }
9201
+ if ( gestureSettings.pinchRotate ) {
9202
+ // Pinch rotate
9203
+ var angle1 = Math.atan2(event.gesturePoints[0].currentPos.y - event.gesturePoints[1].currentPos.y,
9204
+ event.gesturePoints[0].currentPos.x - event.gesturePoints[1].currentPos.x);
9205
+ var angle2 = Math.atan2(event.gesturePoints[0].lastPos.y - event.gesturePoints[1].lastPos.y,
9206
+ event.gesturePoints[0].lastPos.x - event.gesturePoints[1].lastPos.x);
9207
+ this.viewport.setRotation(this.viewport.getRotation() + ((angle1 - angle2) * (180 / Math.PI)));
9208
+ }
8424
9209
  }
8425
9210
  /**
8426
9211
  * Raised when a pinch event occurs on the {@link OpenSeadragon.Viewer#canvas} element.
@@ -8493,97 +9278,64 @@ function onCanvasScroll( event ) {
8493
9278
  return false;
8494
9279
  }
8495
9280
 
8496
- function onContainerExit( event ) {
8497
- if ( !event.insideElementPressed ) {
8498
- THIS[ this.hash ].mouseInside = false;
8499
- if ( !THIS[ this.hash ].animating ) {
8500
- beginControlsAutoHide( this );
8501
- }
8502
- }
9281
+ function onContainerEnter( event ) {
9282
+ THIS[ this.hash ].mouseInside = true;
9283
+ abortControlsAutoHide( this );
8503
9284
  /**
8504
- * Raised when the cursor leaves the {@link OpenSeadragon.Viewer#container} element.
9285
+ * Raised when the cursor enters the {@link OpenSeadragon.Viewer#container} element.
8505
9286
  *
8506
- * @event container-exit
9287
+ * @event container-enter
8507
9288
  * @memberof OpenSeadragon.Viewer
8508
9289
  * @type {object}
8509
9290
  * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
8510
9291
  * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
8511
9292
  * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
8512
9293
  * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
9294
+ * @property {Number} pointers - Number of pointers (all types) active in the tracked element.
8513
9295
  * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
8514
9296
  * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
8515
9297
  * @property {Object} originalEvent - The original DOM event.
8516
9298
  * @property {?Object} userData - Arbitrary subscriber-defined object.
8517
9299
  */
8518
- this.raiseEvent( 'container-exit', {
9300
+ this.raiseEvent( 'container-enter', {
8519
9301
  tracker: event.eventSource,
8520
9302
  position: event.position,
8521
9303
  buttons: event.buttons,
9304
+ pointers: event.pointers,
8522
9305
  insideElementPressed: event.insideElementPressed,
8523
9306
  buttonDownAny: event.buttonDownAny,
8524
9307
  originalEvent: event.originalEvent
8525
9308
  });
8526
9309
  }
8527
9310
 
8528
- function onContainerPress( event ) {
8529
- if ( event.pointerType === 'touch' && !$.MouseTracker.haveTouchEnter ) {
8530
- THIS[ this.hash ].mouseInside = true;
8531
- abortControlsAutoHide( this );
8532
- }
8533
- }
8534
-
8535
- function onContainerRelease( event ) {
8536
- if ( !event.insideElementReleased || ( event.pointerType === 'touch' && !$.MouseTracker.haveTouchEnter ) ) {
9311
+ function onContainerExit( event ) {
9312
+ if ( event.pointers < 1 ) {
8537
9313
  THIS[ this.hash ].mouseInside = false;
8538
9314
  if ( !THIS[ this.hash ].animating ) {
8539
9315
  beginControlsAutoHide( this );
8540
9316
  }
8541
9317
  }
8542
9318
  /**
8543
- * Raised when the mouse button is released or touch ends on the {@link OpenSeadragon.Viewer#container} element.
8544
- *
8545
- * @event container-release
8546
- * @memberof OpenSeadragon.Viewer
8547
- * @type {object}
8548
- * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
8549
- * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
8550
- * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
8551
- * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
8552
- * @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released.
8553
- * @property {Object} originalEvent - The original DOM event.
8554
- * @property {?Object} userData - Arbitrary subscriber-defined object.
8555
- */
8556
- this.raiseEvent( 'container-release', {
8557
- tracker: event.eventSource,
8558
- position: event.position,
8559
- insideElementPressed: event.insideElementPressed,
8560
- insideElementReleased: event.insideElementReleased,
8561
- originalEvent: event.originalEvent
8562
- });
8563
- }
8564
-
8565
- function onContainerEnter( event ) {
8566
- THIS[ this.hash ].mouseInside = true;
8567
- abortControlsAutoHide( this );
8568
- /**
8569
- * Raised when the cursor enters the {@link OpenSeadragon.Viewer#container} element.
9319
+ * Raised when the cursor leaves the {@link OpenSeadragon.Viewer#container} element.
8570
9320
  *
8571
- * @event container-enter
9321
+ * @event container-exit
8572
9322
  * @memberof OpenSeadragon.Viewer
8573
9323
  * @type {object}
8574
9324
  * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
8575
9325
  * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
8576
9326
  * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
8577
9327
  * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
9328
+ * @property {Number} pointers - Number of pointers (all types) active in the tracked element.
8578
9329
  * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
8579
9330
  * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
8580
9331
  * @property {Object} originalEvent - The original DOM event.
8581
9332
  * @property {?Object} userData - Arbitrary subscriber-defined object.
8582
9333
  */
8583
- this.raiseEvent( 'container-enter', {
9334
+ this.raiseEvent( 'container-exit', {
8584
9335
  tracker: event.eventSource,
8585
9336
  position: event.position,
8586
9337
  buttons: event.buttons,
9338
+ pointers: event.pointers,
8587
9339
  insideElementPressed: event.insideElementPressed,
8588
9340
  buttonDownAny: event.buttonDownAny,
8589
9341
  originalEvent: event.originalEvent
@@ -8949,9 +9701,9 @@ function onNext(){
8949
9701
  $.Navigator = function( options ){
8950
9702
 
8951
9703
  var viewer = options.viewer,
9704
+ _this = this,
8952
9705
  viewerSize,
8953
- navigatorSize,
8954
- unneededElement;
9706
+ navigatorSize;
8955
9707
 
8956
9708
  //We may need to create a new element and id if they did not
8957
9709
  //provide the id for the existing element
@@ -8997,6 +9749,7 @@ $.Navigator = function( options ){
8997
9749
  sizeRatio: $.DEFAULT_SETTINGS.navigatorSizeRatio
8998
9750
  }, options, {
8999
9751
  element: this.element,
9752
+ tabIndex: -1, // No keyboard navigation, omit from tab order
9000
9753
  //These need to be overridden to prevent recursion since
9001
9754
  //the navigator is a viewer and a viewer has a navigator
9002
9755
  showNavigator: false,
@@ -9011,6 +9764,8 @@ $.Navigator = function( options ){
9011
9764
 
9012
9765
  options.minPixelRatio = this.minPixelRatio = viewer.minPixelRatio;
9013
9766
 
9767
+ $.setElementTouchActionNone( this.element );
9768
+
9014
9769
  this.borderWidth = 2;
9015
9770
  //At some browser magnification levels the display regions lines up correctly, but at some there appears to
9016
9771
  //be a one pixel gap.
@@ -9058,24 +9813,11 @@ $.Navigator = function( options ){
9058
9813
  style.cursor = 'default';
9059
9814
  }( this.displayRegion.style, this.borderWidth ));
9060
9815
 
9061
-
9062
- this.element.innerTracker = new $.MouseTracker({
9063
- element: this.element,
9064
- dragHandler: $.delegate( this, onCanvasDrag ),
9065
- clickHandler: $.delegate( this, onCanvasClick ),
9066
- releaseHandler: $.delegate( this, onCanvasRelease ),
9067
- scrollHandler: $.delegate( this, onCanvasScroll )
9068
- }).setTracking( true );
9069
-
9070
- /*this.displayRegion.outerTracker = new $.MouseTracker({
9071
- element: this.container,
9072
- clickTimeThreshold: this.clickTimeThreshold,
9073
- clickDistThreshold: this.clickDistThreshold,
9074
- enterHandler: $.delegate( this, onContainerEnter ),
9075
- exitHandler: $.delegate( this, onContainerExit ),
9076
- releaseHandler: $.delegate( this, onContainerRelease )
9077
- }).setTracking( this.mouseNavEnabled ? true : false ); // always tracking*/
9078
-
9816
+ this.displayRegionContainer = $.makeNeutralElement("div");
9817
+ this.displayRegionContainer.id = this.element.id + '-displayregioncontainer';
9818
+ this.displayRegionContainer.className = "displayregioncontainer";
9819
+ this.displayRegionContainer.style.width = "100%";
9820
+ this.displayRegionContainer.style.height = "100%";
9079
9821
 
9080
9822
  viewer.addControl(
9081
9823
  this.element,
@@ -9100,12 +9842,28 @@ $.Navigator = function( options ){
9100
9842
 
9101
9843
  $.Viewer.apply( this, [ options ] );
9102
9844
 
9103
- this.element.getElementsByTagName( 'div' )[0].appendChild( this.displayRegion );
9104
- unneededElement = this.element.getElementsByTagName('textarea')[0];
9105
- if (unneededElement) {
9106
- unneededElement.parentNode.removeChild(unneededElement);
9845
+ this.displayRegionContainer.appendChild(this.displayRegion);
9846
+ this.element.getElementsByTagName('div')[0].appendChild(this.displayRegionContainer);
9847
+
9848
+ if (options.navigatorRotate)
9849
+ {
9850
+ options.viewer.addHandler("rotate", function (args) {
9851
+ _setTransformRotate(_this.displayRegionContainer, args.degrees);
9852
+ _setTransformRotate(_this.displayRegion, -args.degrees);
9853
+ _this.viewport.setRotation(args.degrees);
9854
+ });
9107
9855
  }
9108
9856
 
9857
+ // Remove the base class' (Viewer's) innerTracker and replace it with our own
9858
+ this.innerTracker.destroy();
9859
+ this.innerTracker = new $.MouseTracker({
9860
+ element: this.element,
9861
+ dragHandler: $.delegate( this, onCanvasDrag ),
9862
+ clickHandler: $.delegate( this, onCanvasClick ),
9863
+ releaseHandler: $.delegate( this, onCanvasRelease ),
9864
+ scrollHandler: $.delegate( this, onCanvasScroll )
9865
+ });
9866
+
9109
9867
  };
9110
9868
 
9111
9869
  $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /** @lends OpenSeadragon.Navigator.prototype */{
@@ -9197,12 +9955,9 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
9197
9955
  open: function( source ) {
9198
9956
  this.updateSize();
9199
9957
  var containerSize = this.viewer.viewport.containerSize.times( this.sizeRatio );
9200
- if( source.tileSize > containerSize.x ||
9201
- source.tileSize > containerSize.y ){
9202
- this.minPixelRatio = Math.min(
9203
- containerSize.x,
9204
- containerSize.y
9205
- ) / source.tileSize;
9958
+ var ts = source.getTileSize(source.maxLevel);
9959
+ if ( ts > containerSize.x || ts > containerSize.y ) {
9960
+ this.minPixelRatio = Math.min( containerSize.x, containerSize.y ) / ts;
9206
9961
  } else {
9207
9962
  this.minPixelRatio = this.viewer.minPixelRatio;
9208
9963
  }
@@ -9217,17 +9972,9 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
9217
9972
  * @function
9218
9973
  */
9219
9974
  function onCanvasClick( event ) {
9220
- var newBounds,
9221
- viewerPosition,
9222
- dimensions;
9223
- if (! this.drag) {
9224
- if ( this.viewer.viewport ) {
9225
- this.viewer.viewport.panTo( this.viewport.pointFromPixel( event.position ) );
9226
- this.viewer.viewport.applyConstraints();
9227
- }
9228
- }
9229
- else {
9230
- this.drag = false;
9975
+ if ( event.quick && this.viewer.viewport ) {
9976
+ this.viewer.viewport.panTo( this.viewport.pointFromPixel( event.position ).rotate( -this.viewer.viewport.degrees, this.viewer.viewport.getHomeBounds().getCenter() ) );
9977
+ this.viewer.viewport.applyConstraints();
9231
9978
  }
9232
9979
  }
9233
9980
 
@@ -9238,7 +9985,6 @@ function onCanvasClick( event ) {
9238
9985
  */
9239
9986
  function onCanvasDrag( event ) {
9240
9987
  if ( this.viewer.viewport ) {
9241
- this.drag = true;
9242
9988
  if( !this.panHorizontal ){
9243
9989
  event.delta.x = 0;
9244
9990
  }
@@ -9299,6 +10045,19 @@ function onCanvasScroll( event ) {
9299
10045
  return false;
9300
10046
  }
9301
10047
 
10048
+ /**
10049
+ * @function
10050
+ * @private
10051
+ * @param {Object} element
10052
+ * @param {Number} degrees
10053
+ */
10054
+ function _setTransformRotate (element, degrees) {
10055
+ element.style.webkitTransform = "rotate(" + degrees + "deg)";
10056
+ element.style.mozTransform = "rotate(" + degrees + "deg)";
10057
+ element.style.msTransform = "rotate(" + degrees + "deg)";
10058
+ element.style.oTransform = "rotate(" + degrees + "deg)";
10059
+ element.style.transform = "rotate(" + degrees + "deg)";
10060
+ }
9302
10061
 
9303
10062
  }( OpenSeadragon ));
9304
10063
 
@@ -9749,6 +10508,8 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
9749
10508
  */
9750
10509
  /**
9751
10510
  * The size of the image tiles used to compose the image.
10511
+ * Please note that tileSize may be deprecated in a future release.
10512
+ * Instead the getTileSize(level) function should be used.
9752
10513
  * @member {Number} tileSize
9753
10514
  * @memberof OpenSeadragon.TileSource#
9754
10515
  */
@@ -9816,6 +10577,18 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
9816
10577
 
9817
10578
  $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
9818
10579
 
10580
+ /**
10581
+ * Return the tileSize for a given level.
10582
+ * Subclasses should override this if tileSizes can be different at different levels
10583
+ * such as in IIIFTileSource. Code should use this function rather than reading
10584
+ * from .tileSize directly. tileSize may be deprecated in a future release.
10585
+ * @function
10586
+ * @param {Number} level
10587
+ */
10588
+ getTileSize: function( level ) {
10589
+ return this.tileSize;
10590
+ },
10591
+
9819
10592
  /**
9820
10593
  * @function
9821
10594
  * @param {Number} level
@@ -9842,8 +10615,8 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
9842
10615
  */
9843
10616
  getNumTiles: function( level ) {
9844
10617
  var scale = this.getLevelScale( level ),
9845
- x = Math.ceil( scale * this.dimensions.x / this.tileSize ),
9846
- y = Math.ceil( scale * this.dimensions.y / this.tileSize );
10618
+ x = Math.ceil( scale * this.dimensions.x / this.getTileSize(level) ),
10619
+ y = Math.ceil( scale * this.dimensions.y / this.getTileSize(level) );
9847
10620
 
9848
10621
  return new $.Point( x, y );
9849
10622
  },
@@ -9867,10 +10640,11 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
9867
10640
  */
9868
10641
  getClosestLevel: function( rect ) {
9869
10642
  var i,
9870
- tilesPerSide = Math.floor( Math.max( rect.x, rect.y ) / this.tileSize ),
10643
+ tilesPerSide,
9871
10644
  tiles;
9872
10645
  for( i = this.minLevel; i < this.maxLevel; i++ ){
9873
10646
  tiles = this.getNumTiles( i );
10647
+ tilesPerSide = Math.floor( Math.max( rect.x, rect.y ) / this.getTileSize(i) );
9874
10648
  if( Math.max( tiles.x, tiles.y ) + 1 >= tilesPerSide ){
9875
10649
  break;
9876
10650
  }
@@ -9885,8 +10659,8 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
9885
10659
  */
9886
10660
  getTileAtPoint: function( level, point ) {
9887
10661
  var pixel = point.times( this.dimensions.x ).times( this.getLevelScale(level ) ),
9888
- tx = Math.floor( pixel.x / this.tileSize ),
9889
- ty = Math.floor( pixel.y / this.tileSize );
10662
+ tx = Math.floor( pixel.x / this.getTileSize(level) ),
10663
+ ty = Math.floor( pixel.y / this.getTileSize(level) );
9890
10664
 
9891
10665
  return new $.Point( tx, ty );
9892
10666
  },
@@ -9899,10 +10673,11 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
9899
10673
  */
9900
10674
  getTileBounds: function( level, x, y ) {
9901
10675
  var dimensionsScaled = this.dimensions.times( this.getLevelScale( level ) ),
9902
- px = ( x === 0 ) ? 0 : this.tileSize * x - this.tileOverlap,
9903
- py = ( y === 0 ) ? 0 : this.tileSize * y - this.tileOverlap,
9904
- sx = this.tileSize + ( x === 0 ? 1 : 2 ) * this.tileOverlap,
9905
- sy = this.tileSize + ( y === 0 ? 1 : 2 ) * this.tileOverlap,
10676
+ tileSize = this.getTileSize(level),
10677
+ px = ( x === 0 ) ? 0 : tileSize * x - this.tileOverlap,
10678
+ py = ( y === 0 ) ? 0 : tileSize * y - this.tileOverlap,
10679
+ sx = tileSize + ( x === 0 ? 1 : 2 ) * this.tileOverlap,
10680
+ sy = tileSize + ( y === 0 ? 1 : 2 ) * this.tileOverlap,
9906
10681
  scale = 1.0 / dimensionsScaled.x;
9907
10682
 
9908
10683
  sx = Math.min( sx, dimensionsScaled.x - px );
@@ -10287,8 +11062,10 @@ $.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
10287
11062
  var ns;
10288
11063
  if ( data.Image ) {
10289
11064
  ns = data.Image.xmlns;
10290
- } else if ( data.documentElement && "Image" == data.documentElement.tagName ) {
10291
- ns = data.documentElement.namespaceURI;
11065
+ } else if ( data.documentElement) {
11066
+ if ("Image" == data.documentElement.localName || "Image" == data.documentElement.tagName) {
11067
+ ns = data.documentElement.namespaceURI;
11068
+ }
10292
11069
  }
10293
11070
 
10294
11071
  return ( "http://schemas.microsoft.com/deepzoom/2008" == ns ||
@@ -10318,7 +11095,12 @@ $.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
10318
11095
 
10319
11096
  if (url && !options.tilesUrl) {
10320
11097
  options.tilesUrl = url.replace(/([^\/]+)\.(dzi|xml|js)(\?.*|$)/, '$1_files/');
10321
- options.queryParams = url.match(/\?.*/);
11098
+
11099
+ if (url.search(/\.(dzi|xml|js)\?/) != -1) {
11100
+ options.queryParams = url.match(/\?.*/);
11101
+ }else{
11102
+ options.queryParams = '';
11103
+ }
10322
11104
  }
10323
11105
 
10324
11106
  return options;
@@ -10396,7 +11178,8 @@ function configureFromXML( tileSource, xmlDoc ){
10396
11178
  }
10397
11179
 
10398
11180
  var root = xmlDoc.documentElement,
10399
- rootName = root.tagName,
11181
+ rootName = root.localName || root.tagName,
11182
+ ns = xmlDoc.documentElement.namespaceURI,
10400
11183
  configuration = null,
10401
11184
  displayRects = [],
10402
11185
  dispRectNodes,
@@ -10408,7 +11191,11 @@ function configureFromXML( tileSource, xmlDoc ){
10408
11191
  if ( rootName == "Image" ) {
10409
11192
 
10410
11193
  try {
10411
- sizeNode = root.getElementsByTagName( "Size" )[ 0 ];
11194
+ sizeNode = root.getElementsByTagName("Size" )[ 0 ];
11195
+ if (sizeNode === undefined) {
11196
+ sizeNode = root.getElementsByTagNameNS(ns, "Size" )[ 0 ];
11197
+ }
11198
+
10412
11199
  configuration = {
10413
11200
  Image: {
10414
11201
  xmlns: "http://schemas.microsoft.com/deepzoom/2008",
@@ -10430,10 +11217,17 @@ function configureFromXML( tileSource, xmlDoc ){
10430
11217
  );
10431
11218
  }
10432
11219
 
10433
- dispRectNodes = root.getElementsByTagName( "DisplayRect" );
11220
+ dispRectNodes = root.getElementsByTagName("DisplayRect" );
11221
+ if (dispRectNodes === undefined) {
11222
+ dispRectNodes = root.getElementsByTagNameNS(ns, "DisplayRect" )[ 0 ];
11223
+ }
11224
+
10434
11225
  for ( i = 0; i < dispRectNodes.length; i++ ) {
10435
11226
  dispRectNode = dispRectNodes[ i ];
10436
- rectNode = dispRectNode.getElementsByTagName( "Rect" )[ 0 ];
11227
+ rectNode = dispRectNode.getElementsByTagName("Rect" )[ 0 ];
11228
+ if (rectNode === undefined) {
11229
+ rectNode = dispRectNode.getElementsByTagNameNS(ns, "Rect" )[ 0 ];
11230
+ }
10437
11231
 
10438
11232
  displayRects.push({
10439
11233
  Rect: {
@@ -10509,357 +11303,27 @@ function configureFromObject( tileSource, configuration ){
10509
11303
  parseInt( rectData.Height, 10 ),
10510
11304
  parseInt( rectData.MinLevel, 10 ),
10511
11305
  parseInt( rectData.MaxLevel, 10 )
10512
- ));
10513
- }
10514
-
10515
- return $.extend(true, {
10516
- width: width, /* width *required */
10517
- height: height, /* height *required */
10518
- tileSize: tileSize, /* tileSize *required */
10519
- tileOverlap: tileOverlap, /* tileOverlap *required */
10520
- minLevel: null, /* minLevel */
10521
- maxLevel: null, /* maxLevel */
10522
- tilesUrl: tilesUrl, /* tilesUrl */
10523
- fileFormat: fileFormat, /* fileFormat */
10524
- displayRects: displayRects /* displayRects */
10525
- }, configuration );
10526
-
10527
- }
10528
-
10529
- }( OpenSeadragon ));
10530
-
10531
- /*
10532
- * OpenSeadragon - IIIFTileSource
10533
- *
10534
- * Copyright (C) 2009 CodePlex Foundation
10535
- * Copyright (C) 2010-2013 OpenSeadragon contributors
10536
- *
10537
- * Redistribution and use in source and binary forms, with or without
10538
- * modification, are permitted provided that the following conditions are
10539
- * met:
10540
- *
10541
- * - Redistributions of source code must retain the above copyright notice,
10542
- * this list of conditions and the following disclaimer.
10543
- *
10544
- * - Redistributions in binary form must reproduce the above copyright
10545
- * notice, this list of conditions and the following disclaimer in the
10546
- * documentation and/or other materials provided with the distribution.
10547
- *
10548
- * - Neither the name of CodePlex Foundation nor the names of its
10549
- * contributors may be used to endorse or promote products derived from
10550
- * this software without specific prior written permission.
10551
- *
10552
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
10553
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
10554
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
10555
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
10556
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10557
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
10558
- * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
10559
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
10560
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
10561
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
10562
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
10563
- */
10564
-
10565
- /*
10566
- * The getTileUrl implementation is based on Jon Stroop's Python version,
10567
- * which is released under the New BSD license:
10568
- * https://gist.github.com/jpstroop/4624253
10569
- */
10570
-
10571
-
10572
- (function( $ ){
10573
-
10574
- /**
10575
- * @class IIIFTileSource
10576
- * @classdesc A client implementation of the International Image Interoperability
10577
- * Format: Image API Draft 0.2
10578
- *
10579
- * @memberof OpenSeadragon
10580
- * @extends OpenSeadragon.TileSource
10581
- * @see http://library.stanford.edu/iiif/image-api/
10582
- */
10583
- $.IIIFTileSource = function( options ){
10584
-
10585
- $.extend( true, this, options );
10586
-
10587
- if( !(this.height && this.width && this.identifier && this.tilesUrl ) ){
10588
- throw new Error('IIIF required parameters not provided.');
10589
- }
10590
-
10591
- //TODO: at this point the base tile source implementation assumes
10592
- // a tile is a square and so only has one property tileSize
10593
- // to store it. It may be possible to make tileSize a vector
10594
- // OpenSeadraon.Point but would require careful implementation
10595
- // to preserve backward compatibility.
10596
- options.tileSize = this.tile_width;
10597
-
10598
- if (! options.maxLevel ) {
10599
- var mf = -1;
10600
- var scfs = this.scale_factors || this.scale_factor;
10601
- if ( scfs instanceof Array ) {
10602
- for ( var i = 0; i < scfs.length; i++ ) {
10603
- var cf = Number( scfs[i] );
10604
- if ( !isNaN( cf ) && cf > mf ) { mf = cf; }
10605
- }
10606
- }
10607
- if ( mf < 0 ) { options.maxLevel = Number(Math.ceil(Math.log(Math.max(this.width, this.height), 2))); }
10608
- else { options.maxLevel = mf; }
10609
- }
10610
-
10611
- $.TileSource.apply( this, [ options ] );
10612
- };
10613
-
10614
- $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.IIIFTileSource.prototype */{
10615
- /**
10616
- * Determine if the data and/or url imply the image service is supported by
10617
- * this tile source.
10618
- * @method
10619
- * @param {Object|Array} data
10620
- * @param {String} optional - url
10621
- */
10622
- supports: function( data, url ){
10623
- return (
10624
- data.ns &&
10625
- "http://library.stanford.edu/iiif/image-api/ns/" == data.ns
10626
- ) || (
10627
- data.profile && (
10628
- "http://library.stanford.edu/iiif/image-api/compliance.html#level1" == data.profile ||
10629
- "http://library.stanford.edu/iiif/image-api/compliance.html#level2" == data.profile ||
10630
- "http://library.stanford.edu/iiif/image-api/compliance.html#level3" == data.profile ||
10631
- "http://library.stanford.edu/iiif/image-api/compliance.html" == data.profile
10632
- )
10633
- ) || (
10634
- data.documentElement &&
10635
- "info" == data.documentElement.tagName &&
10636
- "http://library.stanford.edu/iiif/image-api/ns/" ==
10637
- data.documentElement.namespaceURI
10638
- );
10639
- },
10640
-
10641
- /**
10642
- *
10643
- * @method
10644
- * @param {Object|XMLDocument} data - the raw configuration
10645
- * @param {String} url - the url the data was retreived from if any.
10646
- * @return {Object} options - A dictionary of keyword arguments sufficient
10647
- * to configure this tile source via its constructor.
10648
- */
10649
- configure: function( data, url ){
10650
- var service,
10651
- options,
10652
- host;
10653
-
10654
- if( !$.isPlainObject(data) ){
10655
-
10656
- options = configureFromXml( this, data );
10657
-
10658
- }else{
10659
-
10660
- options = configureFromObject( this, data );
10661
- }
10662
-
10663
- if( url && !options.tilesUrl ){
10664
- service = url.split('/');
10665
- service.pop(); //info.json or info.xml
10666
- service = service.join('/');
10667
- if( 'http' !== url.substring( 0, 4 ) ){
10668
- host = location.protocol + '//' + location.host;
10669
- service = host + service;
10670
- }
10671
- options.tilesUrl = service.replace(
10672
- data.identifier,
10673
- ''
10674
- );
10675
- }
10676
-
10677
- return options;
10678
- },
10679
-
10680
- /**
10681
- * Responsible for retreiving the url which will return an image for the
10682
- * region speified by the given x, y, and level components.
10683
- * @method
10684
- * @param {Number} level - z index
10685
- * @param {Number} x
10686
- * @param {Number} y
10687
- * @throws {Error}
10688
- */
10689
- getTileUrl: function( level, x, y ){
10690
-
10691
- //# constants
10692
- var IIIF_ROTATION = '0',
10693
- IIIF_QUALITY = 'native.jpg',
10694
-
10695
- //## get the scale (level as a decimal)
10696
- scale = Math.pow( 0.5, this.maxLevel - level ),
10697
-
10698
- //## get iiif size
10699
- // iiif_size = 'pct:' + ( scale * 100 ),
10700
-
10701
- //# image dimensions at this level
10702
- level_width = Math.ceil( this.width * scale ),
10703
- level_height = Math.ceil( this.height * scale ),
10704
-
10705
- //## iiif region
10706
- iiif_tile_size_width = Math.ceil( this.tileSize / scale ),
10707
- iiif_tile_size_height = Math.ceil( this.tileSize / scale ),
10708
- iiif_region,
10709
- iiif_tile_x,
10710
- iiif_tile_y,
10711
- iiif_tile_w,
10712
- iiif_tile_h,
10713
- iiif_size;
10714
-
10715
-
10716
- if ( level_width < this.tile_width && level_height < this.tile_height ){
10717
- iiif_size = level_width + ","; // + level_height; only one dim. for IIIF level 1 compliance
10718
- iiif_region = 'full';
10719
- } else {
10720
- iiif_tile_x = x * iiif_tile_size_width;
10721
- iiif_tile_y = y * iiif_tile_size_height;
10722
- iiif_tile_w = Math.min( iiif_tile_size_width, this.width - iiif_tile_x );
10723
- iiif_tile_h = Math.min( iiif_tile_size_height, this.height - iiif_tile_y );
10724
- iiif_size = Math.ceil(iiif_tile_w * scale) + ",";
10725
- iiif_region = [ iiif_tile_x, iiif_tile_y, iiif_tile_w, iiif_tile_h ].join(',');
10726
- }
10727
-
10728
- return [
10729
- this.tilesUrl,
10730
- this.identifier,
10731
- iiif_region,
10732
- iiif_size,
10733
- IIIF_ROTATION,
10734
- IIIF_QUALITY
10735
- ].join('/');
10736
- }
10737
-
10738
-
10739
- });
10740
-
10741
- /**
10742
- * @private
10743
- * @inner
10744
- * @function
10745
- * @example
10746
- * <?xml version="1.0" encoding="UTF-8"?>
10747
- * <info xmlns="http://library.stanford.edu/iiif/image-api/ns/">
10748
- * <identifier>1E34750D-38DB-4825-A38A-B60A345E591C</identifier>
10749
- * <width>6000</width>
10750
- * <height>4000</height>
10751
- * <scale_factors>
10752
- * <scale_factor>1</scale_factor>
10753
- * <scale_factor>2</scale_factor>
10754
- * <scale_factor>4</scale_factor>
10755
- * </scale_factors>
10756
- * <tile_width>1024</tile_width>
10757
- * <tile_height>1024</tile_height>
10758
- * <formats>
10759
- * <format>jpg</format>
10760
- * <format>png</format>
10761
- * </formats>
10762
- * <qualities>
10763
- * <quality>native</quality>
10764
- * <quality>grey</quality>
10765
- * </qualities>
10766
- * </info>
10767
- */
10768
- function configureFromXml( tileSource, xmlDoc ){
10769
-
10770
- //parse the xml
10771
- if ( !xmlDoc || !xmlDoc.documentElement ) {
10772
- throw new Error( $.getString( "Errors.Xml" ) );
10773
- }
10774
-
10775
- var root = xmlDoc.documentElement,
10776
- rootName = root.tagName,
10777
- configuration = null;
10778
-
10779
- if ( rootName == "info" ) {
10780
-
10781
- try {
10782
-
10783
- configuration = {
10784
- "ns": root.namespaceURI
10785
- };
10786
-
10787
- parseXML( root, configuration );
10788
-
10789
- return configureFromObject( tileSource, configuration );
10790
-
10791
- } catch ( e ) {
10792
- throw (e instanceof Error) ?
10793
- e :
10794
- new Error( $.getString("Errors.IIIF") );
10795
- }
10796
- }
10797
-
10798
- throw new Error( $.getString( "Errors.IIIF" ) );
10799
-
10800
- }
10801
-
10802
-
10803
- /**
10804
- * @private
10805
- * @inner
10806
- * @function
10807
- */
10808
- function parseXML( node, configuration, property ){
10809
- var i,
10810
- value;
10811
- if( node.nodeType == 3 && property ){//text node
10812
- value = node.nodeValue.trim();
10813
- if( value.match(/^\d*$/)){
10814
- value = Number( value );
10815
- }
10816
- if( !configuration[ property ] ){
10817
- configuration[ property ] = value;
10818
- }else{
10819
- if( !$.isArray( configuration[ property ] ) ){
10820
- configuration[ property ] = [ configuration[ property ] ];
10821
- }
10822
- configuration[ property ].push( value );
10823
- }
10824
- } else if( node.nodeType == 1 ){
10825
- for( i = 0; i < node.childNodes.length; i++ ){
10826
- parseXML( node.childNodes[ i ], configuration, node.nodeName );
10827
- }
10828
- }
10829
- }
10830
-
10831
-
10832
- /**
10833
- * @private
10834
- * @inner
10835
- * @function
10836
- * @example
10837
- * {
10838
- * "profile" : "http://library.stanford.edu/iiif/image-api/compliance.html#level1",
10839
- * "identifier" : "1E34750D-38DB-4825-A38A-B60A345E591C",
10840
- * "width" : 6000,
10841
- * "height" : 4000,
10842
- * "scale_factors" : [ 1, 2, 4 ],
10843
- * "tile_width" : 1024,
10844
- * "tile_height" : 1024,
10845
- * "formats" : [ "jpg", "png" ],
10846
- * "quality" : [ "native", "grey" ]
10847
- * }
10848
- */
10849
- function configureFromObject( tileSource, configuration ){
10850
- //the image_host property is not part of the iiif standard but is included here to
10851
- //allow the info.json and info.xml specify a different server to load the
10852
- //images from so we can test the implementation.
10853
- if( configuration.image_host ){
10854
- configuration.tilesUrl = configuration.image_host;
11306
+ ));
10855
11307
  }
10856
- return configuration;
11308
+
11309
+ return $.extend(true, {
11310
+ width: width, /* width *required */
11311
+ height: height, /* height *required */
11312
+ tileSize: tileSize, /* tileSize *required */
11313
+ tileOverlap: tileOverlap, /* tileOverlap *required */
11314
+ minLevel: null, /* minLevel */
11315
+ maxLevel: null, /* maxLevel */
11316
+ tilesUrl: tilesUrl, /* tilesUrl */
11317
+ fileFormat: fileFormat, /* fileFormat */
11318
+ displayRects: displayRects /* displayRects */
11319
+ }, configuration );
11320
+
10857
11321
  }
10858
11322
 
10859
11323
  }( OpenSeadragon ));
10860
11324
 
10861
11325
  /*
10862
- * OpenSeadragon - IIIF1_1TileSource
11326
+ * OpenSeadragon - IIIFTileSource
10863
11327
  *
10864
11328
  * Copyright (C) 2009 CodePlex Foundation
10865
11329
  * Copyright (C) 2010-2013 OpenSeadragon contributors
@@ -10895,47 +11359,58 @@ function configureFromObject( tileSource, configuration ){
10895
11359
  (function( $ ){
10896
11360
 
10897
11361
  /**
10898
- * @class IIIF1_1TileSource
11362
+ * @class IIIFTileSource
10899
11363
  * @classdesc A client implementation of the International Image Interoperability
10900
- * Format: Image API 1.1
11364
+ * Format: Image API 1.0 - 2.0
10901
11365
  *
10902
11366
  * @memberof OpenSeadragon
10903
11367
  * @extends OpenSeadragon.TileSource
10904
- * @see http://library.stanford.edu/iiif/image-api/
11368
+ * @see http://iiif.io/api/image/
10905
11369
  */
10906
- $.IIIF1_1TileSource = function( options ){
11370
+ $.IIIFTileSource = function( options ){
10907
11371
 
10908
11372
 
10909
11373
  $.extend( true, this, options );
10910
11374
 
10911
-
10912
- if ( !( this.height && this.width && this['@id'] ) ){
11375
+ if ( !( this.height && this.width && this['@id'] ) ) {
10913
11376
  throw new Error( 'IIIF required parameters not provided.' );
10914
11377
  }
10915
11378
 
10916
- if ( ( this.profile &&
10917
- this.profile == "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level0" ) ){
10918
- // what if not reporting a profile?
10919
- throw new Error( 'IIIF Image API 1.1 compliance level 1 or greater is required.' );
10920
- }
11379
+ options.tileSizePerScaleFactor = {};
10921
11380
 
11381
+ // N.B. 2.0 renamed scale_factors to scaleFactors
10922
11382
  if ( this.tile_width ) {
10923
11383
  options.tileSize = this.tile_width;
10924
11384
  } else if ( this.tile_height ) {
10925
11385
  options.tileSize = this.tile_height;
11386
+ } else if ( this.tiles ) {
11387
+ // Version 2.0 forwards
11388
+ if ( this.tiles.length == 1 ) {
11389
+ options.tileSize = this.tiles[0].width;
11390
+ this.scale_factors = this.tiles[0].scaleFactors;
11391
+ } else {
11392
+ // Multiple tile sizes at different levels
11393
+ this.scale_factors = [];
11394
+ for (var t = 0; t < this.tiles.length; t++ ) {
11395
+ for (var sf = 0; sf < this.tiles[t].scaleFactors.length; sf++) {
11396
+ var scaleFactor = this.tiles[t].scaleFactors[sf];
11397
+ this.scale_factors.push(scaleFactor);
11398
+ options.tileSizePerScaleFactor[scaleFactor] = this.tiles[t].width;
11399
+ }
11400
+ }
11401
+ }
10926
11402
  } else {
10927
- // use the largest of tileOptions that is smaller than the short
10928
- // dimension
11403
+ // use the largest of tileOptions that is smaller than the short dimension
10929
11404
 
10930
11405
  var shortDim = Math.min( this.height, this.width ),
10931
11406
  tileOptions = [256,512,1024],
10932
11407
  smallerTiles = [];
10933
11408
 
10934
- for ( var c = 0; c < tileOptions.length; c++ ) {
10935
- if ( tileOptions[c] <= shortDim ) {
10936
- smallerTiles.push( tileOptions[c] );
10937
- }
11409
+ for ( var c = 0; c < tileOptions.length; c++ ) {
11410
+ if ( tileOptions[c] <= shortDim ) {
11411
+ smallerTiles.push( tileOptions[c] );
10938
11412
  }
11413
+ }
10939
11414
 
10940
11415
  if ( smallerTiles.length > 0 ) {
10941
11416
  options.tileSize = Math.max.apply( null, smallerTiles );
@@ -10948,22 +11423,17 @@ $.IIIF1_1TileSource = function( options ){
10948
11423
  }
10949
11424
 
10950
11425
  if ( !options.maxLevel ) {
10951
- var mf = -1;
10952
- var scfs = this.scale_factors || this.scale_factor;
10953
- if ( scfs instanceof Array ) {
10954
- for ( var i = 0; i < scfs.length; i++ ) {
10955
- var cf = Number( scfs[i] );
10956
- if ( !isNaN( cf ) && cf > mf ) { mf = cf; }
10957
- }
11426
+ if ( !this.scale_factors ) {
11427
+ options.maxLevel = Number( Math.ceil( Math.log( Math.max( this.width, this.height ), 2 ) ) );
11428
+ } else {
11429
+ options.maxLevel = Math.floor( Math.pow( Math.max.apply(null, this.scale_factors), 0.5) );
10958
11430
  }
10959
- if ( mf < 0 ) { options.maxLevel = Number( Math.ceil( Math.log( Math.max( this.width, this.height ), 2 ) ) ); }
10960
- else { options.maxLevel = mf; }
10961
11431
  }
10962
11432
 
10963
11433
  $.TileSource.apply( this, [ options ] );
10964
11434
  };
10965
11435
 
10966
- $.extend( $.IIIF1_1TileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.IIIF1_1TileSource.prototype */{
11436
+ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.IIIFTileSource.prototype */{
10967
11437
  /**
10968
11438
  * Determine if the data and/or url imply the image service is supported by
10969
11439
  * this tile source.
@@ -10972,15 +11442,39 @@ $.extend( $.IIIF1_1TileSource.prototype, $.TileSource.prototype, /** @lends Open
10972
11442
  * @param {String} optional - url
10973
11443
  */
10974
11444
  supports: function( data, url ) {
10975
- return ( data['@context'] &&
10976
- data['@context'] == "http://library.stanford.edu/iiif/image-api/1.1/context.json" );
11445
+ // Version 2.0 and forwards
11446
+ if (data.protocol && data.protocol == 'http://iiif.io/api/image') {
11447
+ return true;
11448
+ // Version 1.1
11449
+ } else if ( data['@context'] && (
11450
+ data['@context'] == "http://library.stanford.edu/iiif/image-api/1.1/context.json" ||
11451
+ data['@context'] == "http://iiif.io/api/image/1/context.json") ) {
11452
+ // N.B. the iiif.io context is wrong, but where the representation lives so likely to be used
11453
+ return true;
11454
+
11455
+ // Version 1.0
11456
+ } else if ( data.profile &&
11457
+ data.profile.indexOf("http://library.stanford.edu/iiif/image-api/compliance.html") === 0) {
11458
+ return true;
11459
+ } else if ( data.identifier && data.width && data.height ) {
11460
+ return true;
11461
+ } else if ( data.documentElement &&
11462
+ "info" == data.documentElement.tagName &&
11463
+ "http://library.stanford.edu/iiif/image-api/ns/" ==
11464
+ data.documentElement.namespaceURI) {
11465
+ return true;
11466
+
11467
+ // Not IIIF
11468
+ } else {
11469
+ return false;
11470
+ }
10977
11471
  },
10978
11472
 
10979
11473
  /**
10980
11474
  *
10981
11475
  * @function
10982
11476
  * @param {Object} data - the raw configuration
10983
- * @example <caption>IIIF 1.1 Info Looks like this (XML syntax is no more)</caption>
11477
+ * @example <caption>IIIF 1.1 Info Looks like this</caption>
10984
11478
  * {
10985
11479
  * "@context" : "http://library.stanford.edu/iiif/image-api/1.1/context.json",
10986
11480
  * "@id" : "http://iiif.example.com/prefix/1E34750D-38DB-4825-A38A-B60A345E591C",
@@ -10994,9 +11488,37 @@ $.extend( $.IIIF1_1TileSource.prototype, $.TileSource.prototype, /** @lends Open
10994
11488
  * "profile" : "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level0"
10995
11489
  * }
10996
11490
  */
10997
- configure: function( data ){
10998
- return data;
11491
+ configure: function( data, url ){
11492
+ // Try to deduce our version and fake it upwards if needed
11493
+ if ( !$.isPlainObject(data) ) {
11494
+ var options = configureFromXml10( data );
11495
+ options['@context'] = "http://iiif.io/api/image/1.0/context.json";
11496
+ options['@id'] = url.replace('/info.xml', '');
11497
+ return options;
11498
+ } else if ( !data['@context'] ) {
11499
+ data['@context'] = 'http://iiif.io/api/image/1.0/context.json';
11500
+ data['@id'] = url.replace('/info.json', '');
11501
+ return data;
11502
+ } else {
11503
+ return data;
11504
+ }
11505
+ },
11506
+
11507
+ /**
11508
+ * Return the tileSize for the given level.
11509
+ * @function
11510
+ * @param {Number} level
11511
+ */
11512
+
11513
+ getTileSize: function( level ){
11514
+ var scaleFactor = Math.pow(2, this.maxLevel - level);
11515
+ // cache it in case any external code is going to read it directly
11516
+ if (this.tileSizePerScaleFactor && this.tileSizePerScaleFactor[scaleFactor]) {
11517
+ this.tileSize = this.tileSizePerScaleFactor[scaleFactor];
11518
+ }
11519
+ return this.tileSize;
10999
11520
  },
11521
+
11000
11522
  /**
11001
11523
  * Responsible for retreiving the url which will return an image for the
11002
11524
  * region specified by the given x, y, and level components.
@@ -11009,10 +11531,7 @@ $.extend( $.IIIF1_1TileSource.prototype, $.TileSource.prototype, /** @lends Open
11009
11531
  getTileUrl: function( level, x, y ){
11010
11532
 
11011
11533
  //# constants
11012
-
11013
11534
  var IIIF_ROTATION = '0',
11014
- IIIF_QUALITY = 'native.jpg',
11015
-
11016
11535
  //## get the scale (level as a decimal)
11017
11536
  scale = Math.pow( 0.5, this.maxLevel - level ),
11018
11537
 
@@ -11021,16 +11540,28 @@ $.extend( $.IIIF1_1TileSource.prototype, $.TileSource.prototype, /** @lends Open
11021
11540
  levelHeight = Math.ceil( this.height * scale ),
11022
11541
 
11023
11542
  //## iiif region
11024
- iiifTileSizeWidth = Math.ceil( this.tileSize / scale ),
11025
- iiifTileSizeHeight = Math.ceil( this.tileSize / scale ),
11543
+ iiifTileSizeWidth,
11544
+ iiifTileSizeHeight,
11026
11545
  iiifRegion,
11027
11546
  iiifTileX,
11028
11547
  iiifTileY,
11029
11548
  iiifTileW,
11030
11549
  iiifTileH,
11031
11550
  iiifSize,
11551
+ iiifQuality,
11032
11552
  uri;
11033
11553
 
11554
+ iiifTileSizeWidth = Math.ceil( this.getTileSize(level) / scale );
11555
+ iiifTileSizeHeight = iiifTileSizeWidth;
11556
+
11557
+ if ( this['@context'].indexOf('/1.0/context.json') > -1 ||
11558
+ this['@context'].indexOf('/1.1/context.json') > -1 ||
11559
+ this['@context'].indexOf('/1/context.json') > -1 ) {
11560
+ iiifQuality = "native.jpg";
11561
+ } else {
11562
+ iiifQuality = "default.jpg";
11563
+ }
11564
+
11034
11565
  if ( levelWidth < this.tile_width && levelHeight < this.tile_height ){
11035
11566
  iiifSize = levelWidth + ",";
11036
11567
  iiifRegion = 'full';
@@ -11039,16 +11570,65 @@ $.extend( $.IIIF1_1TileSource.prototype, $.TileSource.prototype, /** @lends Open
11039
11570
  iiifTileY = y * iiifTileSizeHeight;
11040
11571
  iiifTileW = Math.min( iiifTileSizeWidth, this.width - iiifTileX );
11041
11572
  iiifTileH = Math.min( iiifTileSizeHeight, this.height - iiifTileY );
11042
-
11043
11573
  iiifSize = Math.ceil( iiifTileW * scale ) + ",";
11044
-
11045
11574
  iiifRegion = [ iiifTileX, iiifTileY, iiifTileW, iiifTileH ].join( ',' );
11046
11575
  }
11047
- uri = [ this['@id'], iiifRegion, iiifSize, IIIF_ROTATION, IIIF_QUALITY ].join( '/' );
11576
+ uri = [ this['@id'], iiifRegion, iiifSize, IIIF_ROTATION, iiifQuality ].join( '/' );
11577
+
11048
11578
  return uri;
11049
11579
  }
11580
+
11050
11581
  });
11051
11582
 
11583
+ function configureFromXml10(xmlDoc) {
11584
+ //parse the xml
11585
+ if ( !xmlDoc || !xmlDoc.documentElement ) {
11586
+ throw new Error( $.getString( "Errors.Xml" ) );
11587
+ }
11588
+
11589
+ var root = xmlDoc.documentElement,
11590
+ rootName = root.tagName,
11591
+ configuration = null;
11592
+
11593
+ if ( rootName == "info" ) {
11594
+ try {
11595
+ configuration = {};
11596
+ parseXML10( root, configuration );
11597
+ return configuration;
11598
+
11599
+ } catch ( e ) {
11600
+ throw (e instanceof Error) ?
11601
+ e :
11602
+ new Error( $.getString("Errors.IIIF") );
11603
+ }
11604
+ }
11605
+ throw new Error( $.getString( "Errors.IIIF" ) );
11606
+ }
11607
+
11608
+ function parseXML10( node, configuration, property ) {
11609
+ var i,
11610
+ value;
11611
+ if ( node.nodeType == 3 && property ) {//text node
11612
+ value = node.nodeValue.trim();
11613
+ if( value.match(/^\d*$/)){
11614
+ value = Number( value );
11615
+ }
11616
+ if( !configuration[ property ] ){
11617
+ configuration[ property ] = value;
11618
+ }else{
11619
+ if( !$.isArray( configuration[ property ] ) ){
11620
+ configuration[ property ] = [ configuration[ property ] ];
11621
+ }
11622
+ configuration[ property ].push( value );
11623
+ }
11624
+ } else if( node.nodeType == 1 ){
11625
+ for( i = 0; i < node.childNodes.length; i++ ){
11626
+ parseXML10( node.childNodes[ i ], configuration, node.nodeName );
11627
+ }
11628
+ }
11629
+ }
11630
+
11631
+
11052
11632
  }( OpenSeadragon ));
11053
11633
 
11054
11634
  /*
@@ -11924,6 +12504,7 @@ $.Button = function( options ) {
11924
12504
  this.tooltip;
11925
12505
 
11926
12506
  this.element.style.position = "relative";
12507
+ $.setElementTouchActionNone( this.element );
11927
12508
 
11928
12509
  this.imgGroup.style.position =
11929
12510
  this.imgHover.style.position =
@@ -12141,7 +12722,7 @@ $.Button = function( options ) {
12141
12722
  return true;
12142
12723
  }
12143
12724
 
12144
- }).setTracking( true );
12725
+ });
12145
12726
 
12146
12727
  outTo( this, $.ButtonState.REST );
12147
12728
  };
@@ -12375,6 +12956,8 @@ $.ButtonGroup = function( options ) {
12375
12956
  }
12376
12957
  }
12377
12958
 
12959
+ $.setElementTouchActionNone( this.element );
12960
+
12378
12961
  /**
12379
12962
  * Tracks mouse/touch/key events accross the group of buttons.
12380
12963
  * @member {OpenSeadragon.MouseTracker} tracker
@@ -12398,23 +12981,7 @@ $.ButtonGroup = function( options ) {
12398
12981
  }
12399
12982
  }
12400
12983
  },
12401
- pressHandler: function ( event ) {
12402
- if ( event.pointerType === 'touch' && !$.MouseTracker.haveTouchEnter ) {
12403
- var i;
12404
- for ( i = 0; i < _this.buttons.length; i++ ) {
12405
- _this.buttons[ i ].notifyGroupEnter();
12406
- }
12407
- }
12408
- },
12409
- releaseHandler: function ( event ) {
12410
- var i;
12411
- if ( !event.insideElementReleased || ( event.pointerType === 'touch' && !$.MouseTracker.haveTouchEnter ) ) {
12412
- for ( i = 0; i < _this.buttons.length; i++ ) {
12413
- _this.buttons[ i ].notifyGroupExit();
12414
- }
12415
- }
12416
- }
12417
- }).setTracking( true );
12984
+ });
12418
12985
  };
12419
12986
 
12420
12987
  $.ButtonGroup.prototype = /** @lends OpenSeadragon.ButtonGroup.prototype */{
@@ -12623,6 +13190,22 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
12623
13190
  ( this.height === other.height );
12624
13191
  },
12625
13192
 
13193
+ /**
13194
+ * Multiply all dimensions in this Rect by a factor and return a new Rect.
13195
+ * @function
13196
+ * @param {Number} factor The factor to multiply vector components.
13197
+ * @returns {OpenSeadragon.Rect} A new rect representing the multiplication
13198
+ * of the vector components by the factor
13199
+ */
13200
+ times: function( factor ) {
13201
+ return new OpenSeadragon.Rect(
13202
+ this.x * factor,
13203
+ this.y * factor,
13204
+ this.width * factor,
13205
+ this.height * factor
13206
+ );
13207
+ },
13208
+
12626
13209
  /**
12627
13210
  * Rotates a rectangle around a point. Currently only 90, 180, and 270
12628
13211
  * degrees are supported.
@@ -12639,7 +13222,7 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
12639
13222
  newTopLeft;
12640
13223
 
12641
13224
  degrees = ( degrees + 360 ) % 360;
12642
- if( degrees % 90 !== 0 ) {
13225
+ if (degrees % 90 !== 0) {
12643
13226
  throw new Error('Currently only 0, 90, 180, and 270 degrees are supported.');
12644
13227
  }
12645
13228
 
@@ -12813,6 +13396,8 @@ $.ReferenceStrip = function ( options ) {
12813
13396
  style.background = '#000';
12814
13397
  style.position = 'relative';
12815
13398
 
13399
+ $.setElementTouchActionNone( this.element );
13400
+
12816
13401
  $.setElementOpacity( this.element, 0.8 );
12817
13402
 
12818
13403
  this.viewer = viewer;
@@ -12822,8 +13407,9 @@ $.ReferenceStrip = function ( options ) {
12822
13407
  scrollHandler: $.delegate( this, onStripScroll ),
12823
13408
  enterHandler: $.delegate( this, onStripEnter ),
12824
13409
  exitHandler: $.delegate( this, onStripExit ),
13410
+ keyDownHandler: $.delegate( this, onKeyDown ),
12825
13411
  keyHandler: $.delegate( this, onKeyPress )
12826
- } ).setTracking( true );
13412
+ } );
12827
13413
 
12828
13414
  //Controls the position and orientation of the reference strip and sets the
12829
13415
  //appropriate width and height
@@ -12888,6 +13474,7 @@ $.ReferenceStrip = function ( options ) {
12888
13474
  element.style.cssFloat = 'left'; //Firefox
12889
13475
  element.style.styleFloat = 'left'; //IE
12890
13476
  element.style.padding = '2px';
13477
+ $.setElementTouchActionNone( element );
12891
13478
 
12892
13479
  element.innerTracker = new $.MouseTracker( {
12893
13480
  element: element,
@@ -12910,7 +13497,7 @@ $.ReferenceStrip = function ( options ) {
12910
13497
  viewer.goToPage( page );
12911
13498
  }
12912
13499
  }
12913
- } ).setTracking( true );
13500
+ } );
12914
13501
 
12915
13502
  this.element.appendChild( element );
12916
13503
 
@@ -13142,8 +13729,10 @@ function loadPanels( strip, viewerSize, scroll ) {
13142
13729
  style.width = ( strip.panelWidth - 4 ) + 'px';
13143
13730
  style.height = ( strip.panelHeight - 4 ) + 'px';
13144
13731
 
13732
+ // TODO: What is this for? Future keyboard navigation support?
13145
13733
  miniViewer.displayRegion.innerTracker = new $.MouseTracker( {
13146
- element: miniViewer.displayRegion
13734
+ element: miniViewer.displayRegion,
13735
+ startDisabled: true
13147
13736
  } );
13148
13737
 
13149
13738
  element.getElementsByTagName( 'div' )[0].appendChild(
@@ -13207,6 +13796,37 @@ function onStripExit( event ) {
13207
13796
  }
13208
13797
 
13209
13798
 
13799
+ /**
13800
+ * @private
13801
+ * @inner
13802
+ * @function
13803
+ */
13804
+ function onKeyDown( event ) {
13805
+ //console.log( event.keyCode );
13806
+
13807
+ if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
13808
+ switch ( event.keyCode ) {
13809
+ case 38: //up arrow
13810
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
13811
+ return false;
13812
+ case 40: //down arrow
13813
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
13814
+ return false;
13815
+ case 37: //left arrow
13816
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
13817
+ return false;
13818
+ case 39: //right arrow
13819
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
13820
+ return false;
13821
+ default:
13822
+ //console.log( 'navigator keycode %s', event.keyCode );
13823
+ return true;
13824
+ }
13825
+ } else {
13826
+ return true;
13827
+ }
13828
+ }
13829
+
13210
13830
 
13211
13831
  /**
13212
13832
  * @private
@@ -13216,35 +13836,35 @@ function onStripExit( event ) {
13216
13836
  function onKeyPress( event ) {
13217
13837
  //console.log( event.keyCode );
13218
13838
 
13219
- switch ( event.keyCode ) {
13220
- case 61: //=|+
13221
- onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
13222
- return false;
13223
- case 45: //-|_
13224
- onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
13225
- return false;
13226
- case 48: //0|)
13227
- case 119: //w
13228
- case 87: //W
13229
- case 38: //up arrow
13230
- onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
13231
- return false;
13232
- case 115: //s
13233
- case 83: //S
13234
- case 40: //down arrow
13235
- onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
13236
- return false;
13237
- case 97: //a
13238
- case 37: //left arrow
13239
- onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
13240
- return false;
13241
- case 100: //d
13242
- case 39: //right arrow
13243
- onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
13244
- return false;
13245
- default:
13246
- //console.log( 'navigator keycode %s', event.keyCode );
13247
- return true;
13839
+ if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
13840
+ switch ( event.keyCode ) {
13841
+ case 61: //=|+
13842
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
13843
+ return false;
13844
+ case 45: //-|_
13845
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
13846
+ return false;
13847
+ case 48: //0|)
13848
+ case 119: //w
13849
+ case 87: //W
13850
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
13851
+ return false;
13852
+ case 115: //s
13853
+ case 83: //S
13854
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
13855
+ return false;
13856
+ case 97: //a
13857
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
13858
+ return false;
13859
+ case 100: //d
13860
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
13861
+ return false;
13862
+ default:
13863
+ //console.log( 'navigator keycode %s', event.keyCode );
13864
+ return true;
13865
+ }
13866
+ } else {
13867
+ return true;
13248
13868
  }
13249
13869
  }
13250
13870
 
@@ -13430,72 +14050,254 @@ $.Spring = function( options ) {
13430
14050
  * @property {Number} value
13431
14051
  * @property {Number} time
13432
14052
  */
13433
- this.target = {
13434
- value: this.current.value,
13435
- time: this.current.time
13436
- };
13437
- };
14053
+ this.target = {
14054
+ value: this.current.value,
14055
+ time: this.current.time
14056
+ };
14057
+ };
14058
+
14059
+ $.Spring.prototype = /** @lends OpenSeadragon.Spring.prototype */{
14060
+
14061
+ /**
14062
+ * @function
14063
+ * @param {Number} target
14064
+ */
14065
+ resetTo: function( target ) {
14066
+ this.target.value = target;
14067
+ this.target.time = this.current.time;
14068
+ this.start.value = this.target.value;
14069
+ this.start.time = this.target.time;
14070
+ },
14071
+
14072
+ /**
14073
+ * @function
14074
+ * @param {Number} target
14075
+ */
14076
+ springTo: function( target ) {
14077
+ this.start.value = this.current.value;
14078
+ this.start.time = this.current.time;
14079
+ this.target.value = target;
14080
+ this.target.time = this.start.time + 1000 * this.animationTime;
14081
+ },
14082
+
14083
+ /**
14084
+ * @function
14085
+ * @param {Number} delta
14086
+ */
14087
+ shiftBy: function( delta ) {
14088
+ this.start.value += delta;
14089
+ this.target.value += delta;
14090
+ },
14091
+
14092
+ /**
14093
+ * @function
14094
+ */
14095
+ update: function() {
14096
+ this.current.time = $.now();
14097
+ this.current.value = (this.current.time >= this.target.time) ?
14098
+ this.target.value :
14099
+ this.start.value +
14100
+ ( this.target.value - this.start.value ) *
14101
+ transform(
14102
+ this.springStiffness,
14103
+ ( this.current.time - this.start.time ) /
14104
+ ( this.target.time - this.start.time )
14105
+ );
14106
+ }
14107
+ };
14108
+
14109
+ /**
14110
+ * @private
14111
+ */
14112
+ function transform( stiffness, x ) {
14113
+ return ( 1.0 - Math.exp( stiffness * -x ) ) /
14114
+ ( 1.0 - Math.exp( -stiffness ) );
14115
+ }
14116
+
14117
+ }( OpenSeadragon ));
14118
+
14119
+ /*
14120
+ * OpenSeadragon - ImageLoader
14121
+ *
14122
+ * Copyright (C) 2009 CodePlex Foundation
14123
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
14124
+
14125
+ * Redistribution and use in source and binary forms, with or without
14126
+ * modification, are permitted provided that the following conditions are
14127
+ * met:
14128
+ *
14129
+ * - Redistributions of source code must retain the above copyright notice,
14130
+ * this list of conditions and the following disclaimer.
14131
+ *
14132
+ * - Redistributions in binary form must reproduce the above copyright
14133
+ * notice, this list of conditions and the following disclaimer in the
14134
+ * documentation and/or other materials provided with the distribution.
14135
+ *
14136
+ * - Neither the name of CodePlex Foundation nor the names of its
14137
+ * contributors may be used to endorse or promote products derived from
14138
+ * this software without specific prior written permission.
14139
+ *
14140
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
14141
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
14142
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
14143
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
14144
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
14145
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
14146
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
14147
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
14148
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
14149
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
14150
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14151
+ */
14152
+
14153
+ (function( $ ){
14154
+
14155
+ /**
14156
+ * @private
14157
+ * @class ImageJob
14158
+ * @classdesc Handles loading a single image for use in a single {@link OpenSeadragon.Tile}.
14159
+ *
14160
+ * @memberof OpenSeadragon
14161
+ * @param {String} source - URL of image to download.
14162
+ * @param {String} crossOriginPolicy - CORS policy to use for downloads
14163
+ * @param {Function} callback - Called once image has finished downloading.
14164
+ */
14165
+ function ImageJob ( options ) {
14166
+
14167
+ $.extend( true, this, {
14168
+ timeout: $.DEFAULT_SETTINGS.timeout,
14169
+ jobId: null
14170
+ }, options );
14171
+
14172
+ /**
14173
+ * Image object which will contain downloaded image.
14174
+ * @member {Image} image
14175
+ * @memberof OpenSeadragon.ImageJob#
14176
+ */
14177
+ this.image = null;
14178
+ }
13438
14179
 
13439
- $.Spring.prototype = /** @lends OpenSeadragon.Spring.prototype */{
14180
+ ImageJob.prototype = {
13440
14181
 
13441
14182
  /**
13442
- * @function
13443
- * @param {Number} target
14183
+ * Initiates downloading of associated image.
14184
+ * @method
13444
14185
  */
13445
- resetTo: function( target ) {
13446
- this.target.value = target;
13447
- this.target.time = this.current.time;
13448
- this.start.value = this.target.value;
13449
- this.start.time = this.target.time;
13450
- },
14186
+ start: function(){
14187
+ var _this = this;
13451
14188
 
13452
- /**
13453
- * @function
13454
- * @param {Number} target
13455
- */
13456
- springTo: function( target ) {
13457
- this.start.value = this.current.value;
13458
- this.start.time = this.current.time;
13459
- this.target.value = target;
13460
- this.target.time = this.start.time + 1000 * this.animationTime;
14189
+ this.image = new Image();
14190
+
14191
+ if ( this.crossOriginPolicy !== false ) {
14192
+ this.image.crossOrigin = this.crossOriginPolicy;
14193
+ }
14194
+
14195
+ this.image.onload = function(){
14196
+ _this.finish( true );
14197
+ };
14198
+ this.image.onabort = this.image.onerror = function(){
14199
+ _this.finish( false );
14200
+ };
14201
+
14202
+ this.jobId = window.setTimeout( function(){
14203
+ _this.finish( false );
14204
+ }, this.timeout);
14205
+
14206
+ this.image.src = this.src;
13461
14207
  },
13462
14208
 
14209
+ finish: function( successful ) {
14210
+ this.image.onload = this.image.onerror = this.image.onabort = null;
14211
+ if (!successful) {
14212
+ this.image = null;
14213
+ }
14214
+
14215
+ if ( this.jobId ) {
14216
+ window.clearTimeout( this.jobId );
14217
+ }
14218
+
14219
+ this.callback( this );
14220
+ }
14221
+
14222
+ };
14223
+
14224
+ /**
14225
+ * @class
14226
+ * @classdesc Handles downloading of a set of images using asynchronous queue pattern.
14227
+ */
14228
+ $.ImageLoader = function() {
14229
+
14230
+ $.extend( true, this, {
14231
+ jobLimit: $.DEFAULT_SETTINGS.imageLoaderLimit,
14232
+ jobQueue: [],
14233
+ jobsInProgress: 0
14234
+ });
14235
+
14236
+ };
14237
+
14238
+ $.ImageLoader.prototype = {
14239
+
13463
14240
  /**
13464
- * @function
13465
- * @param {Number} delta
14241
+ * Add an unloaded image to the loader queue.
14242
+ * @method
14243
+ * @param {String} src - URL of image to download.
14244
+ * @param {String} crossOriginPolicy - CORS policy to use for downloads
14245
+ * @param {Function} callback - Called once image has been downloaded.
13466
14246
  */
13467
- shiftBy: function( delta ) {
13468
- this.start.value += delta;
13469
- this.target.value += delta;
14247
+ addJob: function( options ) {
14248
+ var _this = this,
14249
+ complete = function( job ) {
14250
+ completeJob( _this, job, options.callback );
14251
+ },
14252
+ jobOptions = {
14253
+ src: options.src,
14254
+ crossOriginPolicy: options.crossOriginPolicy,
14255
+ callback: complete
14256
+ },
14257
+ newJob = new ImageJob( jobOptions );
14258
+
14259
+ if ( !this.jobLimit || this.jobsInProgress < this.jobLimit ) {
14260
+ newJob.start();
14261
+ this.jobsInProgress++;
14262
+ }
14263
+ else {
14264
+ this.jobQueue.push( newJob );
14265
+ }
13470
14266
  },
13471
14267
 
13472
14268
  /**
13473
- * @function
14269
+ * Clear any unstarted image loading jobs from the queue.
14270
+ * @method
13474
14271
  */
13475
- update: function() {
13476
- this.current.time = $.now();
13477
- this.current.value = (this.current.time >= this.target.time) ?
13478
- this.target.value :
13479
- this.start.value +
13480
- ( this.target.value - this.start.value ) *
13481
- transform(
13482
- this.springStiffness,
13483
- ( this.current.time - this.start.time ) /
13484
- ( this.target.time - this.start.time )
13485
- );
14272
+ clear: function() {
14273
+ this.jobQueue = [];
13486
14274
  }
13487
14275
  };
13488
14276
 
13489
14277
  /**
14278
+ * Cleans up ImageJob once completed.
14279
+ * @method
13490
14280
  * @private
14281
+ * @param loader - ImageLoader used to start job.
14282
+ * @param job - The ImageJob that has completed.
14283
+ * @param callback - Called once cleanup is finished.
13491
14284
  */
13492
- function transform( stiffness, x ) {
13493
- return ( 1.0 - Math.exp( stiffness * -x ) ) /
13494
- ( 1.0 - Math.exp( -stiffness ) );
14285
+ function completeJob( loader, job, callback ) {
14286
+ var nextJob;
14287
+
14288
+ loader.jobsInProgress--;
14289
+
14290
+ if ( (!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.jobQueue.length > 0) {
14291
+ nextJob = loader.jobQueue.shift();
14292
+ nextJob.start();
14293
+ }
14294
+
14295
+ callback( job.image );
13495
14296
  }
13496
14297
 
13497
14298
  }( OpenSeadragon ));
13498
14299
 
14300
+
13499
14301
  /*
13500
14302
  * OpenSeadragon - Tile
13501
14303
  *
@@ -14192,7 +14994,7 @@ var DEVICE_SCREEN = $.getWindowSize(),
14192
14994
 
14193
14995
  /**
14194
14996
  * @class Drawer
14195
- * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}.
14997
+ * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}.
14196
14998
  * A new instance is created for each TileSource opened (see {@link OpenSeadragon.Viewer#drawer}).
14197
14999
  *
14198
15000
  * @memberof OpenSeadragon
@@ -14219,7 +15021,7 @@ $.Drawer = function( options ) {
14219
15021
 
14220
15022
  //internal state properties
14221
15023
  viewer: null,
14222
- downloading: 0, // How many images are currently being loaded in parallel.
15024
+ imageLoader: new $.ImageLoader(),
14223
15025
  tilesMatrix: {}, // A '3d' dictionary [level][x][y] --> Tile.
14224
15026
  tilesLoaded: [], // An unordered list of Tiles with loaded images.
14225
15027
  coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean.
@@ -14235,7 +15037,6 @@ $.Drawer = function( options ) {
14235
15037
  //configurable settings
14236
15038
  opacity: $.DEFAULT_SETTINGS.opacity,
14237
15039
  maxImageCacheCount: $.DEFAULT_SETTINGS.maxImageCacheCount,
14238
- imageLoaderLimit: $.DEFAULT_SETTINGS.imageLoaderLimit,
14239
15040
  minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,
14240
15041
  wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,
14241
15042
  wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,
@@ -14439,77 +15240,6 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
14439
15240
  return this;
14440
15241
  },
14441
15242
 
14442
- /**
14443
- * Used internally to load images when required. May also be used to
14444
- * preload a set of images so the browser will have them available in
14445
- * the local cache to optimize user experience in certain cases. Because
14446
- * the number of parallel image loads is configurable, if too many images
14447
- * are currently being loaded, the request will be ignored. Since by
14448
- * default drawer.imageLoaderLimit is 0, the native browser parallel
14449
- * image loading policy will be used.
14450
- * @method
14451
- * @param {String} src - The url of the image to load.
14452
- * @param {Function} callback - The function that will be called with the
14453
- * Image object as the only parameter if it was loaded successfully.
14454
- * If an error occured, or the request timed out or was aborted,
14455
- * the parameter is null instead.
14456
- * @return {Boolean} loading - Whether the request was submitted or ignored
14457
- * based on OpenSeadragon.DEFAULT_SETTINGS.imageLoaderLimit.
14458
- */
14459
- loadImage: function( src, callback ) {
14460
- var _this = this,
14461
- loading = false,
14462
- image,
14463
- jobid,
14464
- complete;
14465
-
14466
- if ( !this.imageLoaderLimit ||
14467
- this.downloading < this.imageLoaderLimit ) {
14468
-
14469
- this.downloading++;
14470
-
14471
- image = new Image();
14472
-
14473
- if ( _this.crossOriginPolicy !== false ) {
14474
- image.crossOrigin = _this.crossOriginPolicy;
14475
- }
14476
-
14477
- complete = function( imagesrc, resultingImage ){
14478
- _this.downloading--;
14479
- if (typeof ( callback ) == "function") {
14480
- try {
14481
- callback( resultingImage );
14482
- } catch ( e ) {
14483
- $.console.error(
14484
- "%s while executing %s callback: %s",
14485
- e.name,
14486
- src,
14487
- e.message,
14488
- e
14489
- );
14490
- }
14491
- }
14492
- };
14493
-
14494
- image.onload = function(){
14495
- finishLoadingImage( image, complete, true, jobid );
14496
- };
14497
-
14498
- image.onabort = image.onerror = function(){
14499
- finishLoadingImage( image, complete, false, jobid );
14500
- };
14501
-
14502
- jobid = window.setTimeout( function(){
14503
- finishLoadingImage( image, complete, false, jobid );
14504
- }, this.timeout );
14505
-
14506
- loading = true;
14507
- image.src = src;
14508
- }
14509
-
14510
- return loading;
14511
- },
14512
-
14513
15243
  /**
14514
15244
  * Returns whether rotation is supported or not.
14515
15245
  * @method
@@ -14517,6 +15247,22 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
14517
15247
  */
14518
15248
  canRotate: function() {
14519
15249
  return this.useCanvas;
15250
+ },
15251
+
15252
+ /**
15253
+ * Destroy the drawer (unload current loaded tiles)
15254
+ * @method
15255
+ * @return null
15256
+ */
15257
+ destroy: function() {
15258
+ //unload current loaded tiles (=empty TILE_CACHE)
15259
+ for ( var i = 0; i < this.tilesLoaded.length; ++i ) {
15260
+ this.tilesLoaded[i].unload();
15261
+ }
15262
+
15263
+ //force unloading of current canvas (1x1 will be gc later, trick not necessarily needed)
15264
+ this.canvas.width = 1;
15265
+ this.canvas.height = 1;
14520
15266
  }
14521
15267
  };
14522
15268
 
@@ -14579,13 +15325,13 @@ function updateViewport( drawer ) {
14579
15325
  levelOpacity,
14580
15326
  levelVisibility;
14581
15327
 
14582
- //TODO
15328
+ // Reset tile's internal drawn state
14583
15329
  while ( drawer.lastDrawn.length > 0 ) {
14584
15330
  tile = drawer.lastDrawn.pop();
14585
15331
  tile.beingDrawn = false;
14586
15332
  }
14587
15333
 
14588
- //TODO
15334
+ // Clear canvas
14589
15335
  drawer.canvas.innerHTML = "";
14590
15336
  if ( drawer.useCanvas ) {
14591
15337
  if( drawer.canvas.width != viewportSize.x ||
@@ -14602,6 +15348,16 @@ function updateViewport( drawer ) {
14602
15348
  viewportTL = rotatedBounds.getTopLeft();
14603
15349
  viewportBR = rotatedBounds.getBottomRight();
14604
15350
  }
15351
+ else if (degrees !== 0) {
15352
+ // This is just an approximation.
15353
+ var orthBounds = viewportBounds.rotate(90);
15354
+ viewportBounds.x -= orthBounds.width / 2;
15355
+ viewportBounds.y -= orthBounds.height / 2;
15356
+ viewportBounds.width += orthBounds.width;
15357
+ viewportBounds.height += orthBounds.height;
15358
+ viewportTL = viewportBounds.getTopLeft();
15359
+ viewportBR = viewportBounds.getBottomRight();
15360
+ }
14605
15361
 
14606
15362
  //Don't draw if completely outside of the viewport
14607
15363
  if ( !drawer.wrapHorizontal &&
@@ -14613,7 +15369,7 @@ function updateViewport( drawer ) {
14613
15369
  return;
14614
15370
  }
14615
15371
 
14616
- //TODO
15372
+ // Calculate viewport rect / bounds
14617
15373
  if ( !drawer.wrapHorizontal ) {
14618
15374
  viewportTL.x = Math.max( viewportTL.x, 0 );
14619
15375
  viewportBR.x = Math.min( viewportBR.x, 1 );
@@ -14623,10 +15379,12 @@ function updateViewport( drawer ) {
14623
15379
  viewportBR.y = Math.min( viewportBR.y, drawer.normHeight );
14624
15380
  }
14625
15381
 
14626
- //TODO
15382
+ // Calculations for the interval of levels to draw
15383
+ // (above in initial var statement)
15384
+ // can return invalid intervals; fix that here if necessary
14627
15385
  lowestLevel = Math.min( lowestLevel, highestLevel );
14628
15386
 
14629
- //TODO
15387
+ // Update any level that will be drawn
14630
15388
  var drawLevel; // FIXME: drawLevel should have a more explanatory name
14631
15389
  for ( level = highestLevel; level >= lowestLevel; level-- ) {
14632
15390
  drawLevel = false;
@@ -14671,7 +15429,7 @@ function updateViewport( drawer ) {
14671
15429
  optimalRatio - renderPixelRatioT
14672
15430
  );
14673
15431
 
14674
- //TODO
15432
+ // Update the level and keep track of 'best' tile to load
14675
15433
  best = updateLevel(
14676
15434
  drawer,
14677
15435
  haveDrawn,
@@ -14685,16 +15443,17 @@ function updateViewport( drawer ) {
14685
15443
  best
14686
15444
  );
14687
15445
 
14688
- //TODO
15446
+ // Stop the loop if lower-res tiles would all be covered by
15447
+ // already drawn tiles
14689
15448
  if ( providesCoverage( drawer.coverage, level ) ) {
14690
15449
  break;
14691
15450
  }
14692
15451
  }
14693
15452
 
14694
- //TODO
15453
+ // Perform the actual drawing
14695
15454
  drawTiles( drawer, drawer.lastDrawn );
14696
15455
 
14697
- //TODO
15456
+ // Load the new 'best' tile
14698
15457
  if ( best ) {
14699
15458
  loadTile( drawer, best, currentTime );
14700
15459
  // because we haven't finished drawing, so
@@ -14899,39 +15658,27 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, normHeig
14899
15658
  return tile;
14900
15659
  }
14901
15660
 
14902
-
14903
15661
  function loadTile( drawer, tile, time ) {
14904
15662
  if( drawer.viewport.collectionMode ){
14905
15663
  drawer.midUpdate = false;
14906
15664
  onTileLoad( drawer, tile, time );
14907
15665
  } else {
14908
- tile.loading = drawer.loadImage(
14909
- tile.url,
14910
- function( image ){
15666
+ tile.loading = true;
15667
+ drawer.imageLoader.addJob({
15668
+ src: tile.url,
15669
+ crossOriginPolicy: drawer.crossOriginPolicy,
15670
+ callback: function( image ){
14911
15671
  onTileLoad( drawer, tile, time, image );
14912
15672
  }
14913
- );
15673
+ });
14914
15674
  }
14915
15675
  }
14916
15676
 
14917
15677
  function onTileLoad( drawer, tile, time, image ) {
14918
- var insertionIndex,
14919
- cutoff,
14920
- worstTile,
14921
- worstTime,
14922
- worstLevel,
14923
- worstTileIndex,
14924
- prevTile,
14925
- prevTime,
14926
- prevLevel,
14927
- i;
14928
15678
 
14929
15679
  tile.loading = false;
14930
15680
 
14931
- if ( drawer.midUpdate ) {
14932
- $.console.warn( "Tile load callback in middle of drawing routine." );
14933
- return;
14934
- } else if ( !image && !drawer.viewport.collectionMode ) {
15681
+ if ( !image && !drawer.viewport.collectionMode ) {
14935
15682
  $.console.log( "Tile %s failed to load: %s", tile, tile.url );
14936
15683
  if( !drawer.debugMode ){
14937
15684
  tile.exists = false;
@@ -14945,46 +15692,62 @@ function onTileLoad( drawer, tile, time, image ) {
14945
15692
  tile.loaded = true;
14946
15693
  tile.image = image;
14947
15694
 
15695
+ if ( drawer.tilesLoaded.length < drawer.maxImageCacheCount ) {
15696
+ // always safe to append things to cache
15697
+ drawer.tilesLoaded[ drawer.tilesLoaded.length ] = tile;
15698
+ }
15699
+ else {
15700
+ // need to remove something from cache,
15701
+ // make sure this doesn't happen mid update
15702
+ if ( !drawer.midUpdate ) {
15703
+ updateTileCache( tile, drawer );
15704
+ }
15705
+ else {
15706
+ window.setTimeout( function() {
15707
+ updateTileCache( tile, drawer );
15708
+ }, 1);
15709
+ }
15710
+ }
14948
15711
 
14949
- insertionIndex = drawer.tilesLoaded.length;
14950
-
14951
- if ( drawer.tilesLoaded.length >= drawer.maxImageCacheCount ) {
14952
- cutoff = Math.ceil( Math.log( drawer.source.tileSize ) / Math.log( 2 ) );
15712
+ drawer.updateAgain = true;
15713
+ }
14953
15714
 
14954
- worstTile = null;
15715
+ function updateTileCache( newTile, drawer ) {
15716
+ var i, prevTile, prevTime, worstTime, prevLevel, worstLevel,
15717
+ insertionIndex = drawer.tilesLoaded.length,
15718
+ cutoff = Math.ceil( Math.log( drawer.source.getTileSize(newTile.level) ) / Math.log( 2 ) ),
15719
+ worstTile = null,
14955
15720
  worstTileIndex = -1;
14956
15721
 
14957
- for ( i = drawer.tilesLoaded.length - 1; i >= 0; i-- ) {
14958
- prevTile = drawer.tilesLoaded[ i ];
15722
+ for ( i = drawer.tilesLoaded.length - 1; i >= 0; i-- ) {
15723
+ prevTile = drawer.tilesLoaded[ i ];
14959
15724
 
14960
- if ( prevTile.level <= drawer.cutoff || prevTile.beingDrawn ) {
14961
- continue;
14962
- } else if ( !worstTile ) {
14963
- worstTile = prevTile;
14964
- worstTileIndex = i;
14965
- continue;
14966
- }
15725
+ if ( prevTile.level <= drawer.cutoff || prevTile.beingDrawn ) {
15726
+ continue;
15727
+ } else if ( !worstTile ) {
15728
+ worstTile = prevTile;
15729
+ worstTileIndex = i;
15730
+ continue;
15731
+ }
14967
15732
 
14968
- prevTime = prevTile.lastTouchTime;
14969
- worstTime = worstTile.lastTouchTime;
14970
- prevLevel = prevTile.level;
14971
- worstLevel = worstTile.level;
15733
+ prevTime = prevTile.lastTouchTime;
15734
+ worstTime = worstTile.lastTouchTime;
15735
+ prevLevel = prevTile.level;
15736
+ worstLevel = worstTile.level;
14972
15737
 
14973
- if ( prevTime < worstTime ||
14974
- ( prevTime == worstTime && prevLevel > worstLevel ) ) {
14975
- worstTile = prevTile;
14976
- worstTileIndex = i;
14977
- }
14978
- }
15738
+ if ( prevTime < worstTime ||
15739
+ ( prevTime == worstTime && prevLevel > worstLevel ) ) {
15740
+ worstTile = prevTile;
15741
+ worstTileIndex = i;
15742
+ }
15743
+ }
14979
15744
 
14980
- if ( worstTile && worstTileIndex >= 0 ) {
14981
- worstTile.unload();
14982
- insertionIndex = worstTileIndex;
14983
- }
15745
+ if ( worstTile && worstTileIndex >= 0 ) {
15746
+ worstTile.unload();
15747
+ insertionIndex = worstTileIndex;
14984
15748
  }
14985
15749
 
14986
- drawer.tilesLoaded[ insertionIndex ] = tile;
14987
- drawer.updateAgain = true;
15750
+ drawer.tilesLoaded[ insertionIndex ] = newTile;
14988
15751
  }
14989
15752
 
14990
15753
 
@@ -15161,21 +15924,6 @@ function compareTiles( previousBest, tile ) {
15161
15924
  return previousBest;
15162
15925
  }
15163
15926
 
15164
- function finishLoadingImage( image, callback, successful, jobid ){
15165
-
15166
- image.onload = null;
15167
- image.onabort = null;
15168
- image.onerror = null;
15169
-
15170
- if ( jobid ) {
15171
- window.clearTimeout( jobid );
15172
- }
15173
- $.requestAnimationFrame( function() {
15174
- callback( image.src, successful ? image : null);
15175
- });
15176
-
15177
- }
15178
-
15179
15927
  function drawTiles( drawer, lastDrawn ){
15180
15928
  var i,
15181
15929
  tile,
@@ -15350,12 +16098,24 @@ function drawDebugInfo( drawer, tile, count, i ){
15350
16098
  drawer.context.font = 'small-caps bold 13px ariel';
15351
16099
  drawer.context.strokeStyle = drawer.debugGridColor;
15352
16100
  drawer.context.fillStyle = drawer.debugGridColor;
16101
+
16102
+ offsetForRotation( tile, drawer.canvas, drawer.context, drawer.viewport.degrees );
16103
+
15353
16104
  drawer.context.strokeRect(
15354
16105
  tile.position.x,
15355
16106
  tile.position.y,
15356
16107
  tile.size.x,
15357
16108
  tile.size.y
15358
16109
  );
16110
+
16111
+ var tileCenterX = tile.position.x + (tile.size.x / 2);
16112
+ var tileCenterY = tile.position.y + (tile.size.y / 2);
16113
+
16114
+ // Rotate the text the right way around.
16115
+ drawer.context.translate( tileCenterX, tileCenterY );
16116
+ drawer.context.rotate( Math.PI / 180 * -drawer.viewport.degrees );
16117
+ drawer.context.translate( -tileCenterX, -tileCenterY );
16118
+
15359
16119
  if( tile.x === 0 && tile.y === 0 ){
15360
16120
  drawer.context.fillText(
15361
16121
  "Zoom: " + drawer.viewport.getZoom(),
@@ -15398,6 +16158,7 @@ function drawDebugInfo( drawer, tile, count, i ){
15398
16158
  tile.position.x + 10,
15399
16159
  tile.position.y + 70
15400
16160
  );
16161
+ restoreRotationChanges( tile, drawer.canvas, drawer.context );
15401
16162
  drawer.context.restore();
15402
16163
  }
15403
16164
  }
@@ -15491,7 +16252,8 @@ $.Viewport = function( options ) {
15491
16252
  defaultZoomLevel: $.DEFAULT_SETTINGS.defaultZoomLevel,
15492
16253
  minZoomLevel: $.DEFAULT_SETTINGS.minZoomLevel,
15493
16254
  maxZoomLevel: $.DEFAULT_SETTINGS.maxZoomLevel,
15494
- degrees: $.DEFAULT_SETTINGS.degrees
16255
+ degrees: $.DEFAULT_SETTINGS.degrees,
16256
+ homeFillsViewer: $.DEFAULT_SETTINGS.homeFillsViewer
15495
16257
 
15496
16258
  }, options );
15497
16259
 
@@ -15555,15 +16317,21 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
15555
16317
  * @function
15556
16318
  */
15557
16319
  getHomeZoom: function() {
15558
- var aspectFactor =
15559
- this.contentAspectX / this.getAspectRatio();
15560
-
15561
16320
  if( this.defaultZoomLevel ){
15562
16321
  return this.defaultZoomLevel;
15563
16322
  } else {
15564
- return ( aspectFactor >= 1 ) ?
15565
- 1 :
15566
- aspectFactor;
16323
+ var aspectFactor =
16324
+ this.contentAspectX / this.getAspectRatio();
16325
+
16326
+ if( this.homeFillsViewer ){ // fill the viewer and clip the image
16327
+ return ( aspectFactor >= 1) ?
16328
+ aspectFactor :
16329
+ 1;
16330
+ } else {
16331
+ return ( aspectFactor >= 1 ) ?
16332
+ 1 :
16333
+ aspectFactor;
16334
+ }
15567
16335
  }
15568
16336
  },
15569
16337
 
@@ -15729,38 +16497,34 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
15729
16497
 
15730
16498
  /**
15731
16499
  * @function
15732
- * @return {OpenSeadragon.Viewport} Chainable.
15733
- * @fires OpenSeadragon.Viewer.event:constrain
16500
+ * @private
16501
+ * @param {OpenSeadragon.Rect} bounds
16502
+ * @param {Boolean} immediately
16503
+ * @return {OpenSeadragon.Rect} constrained bounds.
15734
16504
  */
15735
- applyConstraints: function( immediately ) {
15736
- var actualZoom = this.getZoom(),
15737
- constrainedZoom = Math.max(
15738
- Math.min( actualZoom, this.getMaxZoom() ),
15739
- this.getMinZoom()
15740
- ),
15741
- bounds,
15742
- horizontalThreshold,
16505
+ _applyBoundaryConstraints: function( bounds, immediately ) {
16506
+ var horizontalThreshold,
15743
16507
  verticalThreshold,
15744
16508
  left,
15745
16509
  right,
15746
16510
  top,
15747
16511
  bottom,
15748
16512
  dx = 0,
15749
- dy = 0;
15750
-
15751
- if ( actualZoom != constrainedZoom ) {
15752
- this.zoomTo( constrainedZoom, this.zoomPoint, immediately );
15753
- }
15754
-
15755
- bounds = this.getBounds();
16513
+ dy = 0,
16514
+ newBounds = new $.Rect(
16515
+ bounds.x,
16516
+ bounds.y,
16517
+ bounds.width,
16518
+ bounds.height
16519
+ );
15756
16520
 
15757
- horizontalThreshold = this.visibilityRatio * bounds.width;
15758
- verticalThreshold = this.visibilityRatio * bounds.height;
16521
+ horizontalThreshold = this.visibilityRatio * newBounds.width;
16522
+ verticalThreshold = this.visibilityRatio * newBounds.height;
15759
16523
 
15760
- left = bounds.x + bounds.width;
15761
- right = 1 - bounds.x;
15762
- top = bounds.y + bounds.height;
15763
- bottom = this.contentAspectY - bounds.y;
16524
+ left = newBounds.x + newBounds.width;
16525
+ right = 1 - newBounds.x;
16526
+ top = newBounds.y + newBounds.height;
16527
+ bottom = this.contentAspectY - newBounds.y;
15764
16528
 
15765
16529
  if ( this.wrapHorizontal ) {
15766
16530
  //do nothing
@@ -15789,15 +16553,14 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
15789
16553
  }
15790
16554
 
15791
16555
  if ( dx || dy || immediately ) {
15792
- bounds.x += dx;
15793
- bounds.y += dy;
15794
- if( bounds.width > 1 ){
15795
- bounds.x = 0.5 - bounds.width/2;
16556
+ newBounds.x += dx;
16557
+ newBounds.y += dy;
16558
+ if( newBounds.width > 1 ){
16559
+ newBounds.x = 0.5 - newBounds.width/2;
15796
16560
  }
15797
- if( bounds.height > this.contentAspectY ){
15798
- bounds.y = this.contentAspectY/2 - bounds.height/2;
16561
+ if( newBounds.height > this.contentAspectY ){
16562
+ newBounds.y = this.contentAspectY/2 - newBounds.height/2;
15799
16563
  }
15800
- this.fitBounds( bounds, immediately );
15801
16564
  }
15802
16565
 
15803
16566
  if( this.viewer ){
@@ -15816,6 +16579,35 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
15816
16579
  });
15817
16580
  }
15818
16581
 
16582
+ return newBounds;
16583
+ },
16584
+
16585
+ /**
16586
+ * @function
16587
+ * @return {OpenSeadragon.Viewport} Chainable.
16588
+ * @fires OpenSeadragon.Viewer.event:constrain
16589
+ */
16590
+ applyConstraints: function( immediately ) {
16591
+ var actualZoom = this.getZoom(),
16592
+ constrainedZoom = Math.max(
16593
+ Math.min( actualZoom, this.getMaxZoom() ),
16594
+ this.getMinZoom()
16595
+ ),
16596
+ bounds,
16597
+ constrainedBounds;
16598
+
16599
+ if ( actualZoom != constrainedZoom ) {
16600
+ this.zoomTo( constrainedZoom, this.zoomPoint, immediately );
16601
+ }
16602
+
16603
+ bounds = this.getBounds();
16604
+
16605
+ constrainedBounds = this._applyBoundaryConstraints( bounds, immediately );
16606
+
16607
+ if ( bounds.x !== constrainedBounds.x || bounds.y !== constrainedBounds.y || immediately ){
16608
+ this.fitBounds( constrainedBounds, immediately );
16609
+ }
16610
+
15819
16611
  return this;
15820
16612
  },
15821
16613
 
@@ -15829,11 +16621,16 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
15829
16621
 
15830
16622
  /**
15831
16623
  * @function
16624
+ * @private
15832
16625
  * @param {OpenSeadragon.Rect} bounds
15833
- * @param {Boolean} immediately
16626
+ * @param {Object} options (immediately=false, constraints=false)
15834
16627
  * @return {OpenSeadragon.Viewport} Chainable.
15835
16628
  */
15836
- fitBounds: function( bounds, immediately ) {
16629
+ _fitBounds: function( bounds, options ) {
16630
+ options = options || {};
16631
+ var immediately = options.immediately || false;
16632
+ var constraints = options.constraints || false;
16633
+
15837
16634
  var aspect = this.getAspectRatio(),
15838
16635
  center = bounds.getCenter(),
15839
16636
  newBounds = new $.Rect(
@@ -15845,7 +16642,9 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
15845
16642
  oldBounds,
15846
16643
  oldZoom,
15847
16644
  newZoom,
15848
- referencePoint;
16645
+ referencePoint,
16646
+ newBoundsAspectRatio,
16647
+ newConstrainedZoom;
15849
16648
 
15850
16649
  if ( newBounds.getAspectRatio() >= aspect ) {
15851
16650
  newBounds.height = bounds.width / aspect;
@@ -15855,14 +16654,36 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
15855
16654
  newBounds.x = center.x - newBounds.width / 2;
15856
16655
  }
15857
16656
 
16657
+ if ( constraints ) {
16658
+ newBoundsAspectRatio = newBounds.getAspectRatio();
16659
+ }
16660
+
15858
16661
  this.panTo( this.getCenter( true ), true );
15859
16662
  this.zoomTo( this.getZoom( true ), null, true );
15860
16663
 
15861
16664
  oldBounds = this.getBounds();
15862
16665
  oldZoom = this.getZoom();
15863
16666
  newZoom = 1.0 / newBounds.width;
16667
+
16668
+ if ( constraints ) {
16669
+ newConstrainedZoom = Math.max(
16670
+ Math.min(newZoom, this.getMaxZoom() ),
16671
+ this.getMinZoom()
16672
+ );
16673
+
16674
+ if (newZoom !== newConstrainedZoom) {
16675
+ newZoom = newConstrainedZoom;
16676
+ newBounds.width = 1.0 / newZoom;
16677
+ newBounds.x = center.x - newBounds.width / 2;
16678
+ newBounds.height = newBounds.width / newBoundsAspectRatio;
16679
+ newBounds.y = center.y - newBounds.height / 2;
16680
+ }
16681
+
16682
+ newBounds = this._applyBoundaryConstraints( newBounds, immediately );
16683
+ }
16684
+
15864
16685
  if ( newZoom == oldZoom || newBounds.width == oldBounds.width ) {
15865
- return this.panTo( center, immediately );
16686
+ return this.panTo( constraints ? newBounds.getCenter() : center, immediately );
15866
16687
  }
15867
16688
 
15868
16689
  referencePoint = oldBounds.getTopLeft().times(
@@ -15879,6 +16700,31 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
15879
16700
  return this.zoomTo( newZoom, referencePoint, immediately );
15880
16701
  },
15881
16702
 
16703
+ /**
16704
+ * @function
16705
+ * @param {OpenSeadragon.Rect} bounds
16706
+ * @param {Boolean} immediately
16707
+ * @return {OpenSeadragon.Viewport} Chainable.
16708
+ */
16709
+ fitBounds: function( bounds, immediately ) {
16710
+ return this._fitBounds( bounds, {
16711
+ immediately: immediately,
16712
+ constraints: false
16713
+ } );
16714
+ },
16715
+
16716
+ /**
16717
+ * @function
16718
+ * @param {OpenSeadragon.Rect} bounds
16719
+ * @param {Boolean} immediately
16720
+ * @return {OpenSeadragon.Viewport} Chainable.
16721
+ */
16722
+ fitBoundsWithConstraints: function( bounds, immediately ) {
16723
+ return this._fitBounds( bounds, {
16724
+ immediately: immediately,
16725
+ constraints: true
16726
+ } );
16727
+ },
15882
16728
 
15883
16729
  /**
15884
16730
  * @function
@@ -16042,10 +16888,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
16042
16888
  },
16043
16889
 
16044
16890
  /**
16045
- * Currently only 90 degree rotation is supported and it only works
16046
- * with the canvas. Additionally, the navigator does not rotate yet,
16047
- * debug mode doesn't rotate yet, and overlay rotation is only
16048
- * partially supported.
16891
+ * Rotates this viewport to the angle specified.
16049
16892
  * @function
16050
16893
  * @return {OpenSeadragon.Viewport} Chainable.
16051
16894
  */
@@ -16055,12 +16898,23 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
16055
16898
  }
16056
16899
 
16057
16900
  degrees = ( degrees + 360 ) % 360;
16058
- if( degrees % 90 !== 0 ) {
16059
- throw new Error('Currently only 0, 90, 180, and 270 degrees are supported.');
16060
- }
16061
16901
  this.degrees = degrees;
16062
16902
  this.viewer.forceRedraw();
16063
-
16903
+
16904
+ /**
16905
+ * Raised when rotation has been changed.
16906
+ *
16907
+ * @event update-viewport
16908
+ * @memberof OpenSeadragon.Viewer
16909
+ * @type {object}
16910
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
16911
+ * @property {Number} degrees - The number of degrees the rotation was set to.
16912
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
16913
+ */
16914
+ if (this.viewer !== null)
16915
+ {
16916
+ this.viewer.raiseEvent('rotate', {"degrees": degrees});
16917
+ }
16064
16918
  return this;
16065
16919
  },
16066
16920
 
@@ -16398,7 +17252,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
16398
17252
  return viewerCoordinates.plus(
16399
17253
  OpenSeadragon.getElementPosition( this.viewer.element ));
16400
17254
  },
16401
-
17255
+
16402
17256
  /**
16403
17257
  * Convert a viewport zoom to an image zoom.
16404
17258
  * Image zoom: ratio of the original image size to displayed image size.
@@ -16416,7 +17270,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
16416
17270
  var viewportToImageZoomRatio = containerWidth / imageWidth;
16417
17271
  return viewportZoom * viewportToImageZoomRatio;
16418
17272
  },
16419
-
17273
+
16420
17274
  /**
16421
17275
  * Convert an image zoom to a viewport zoom.
16422
17276
  * Image zoom: ratio of the original image size to displayed image size.