diff --git a/build.properties b/build.properties
index 2fa88b2e..f245e5be 100644
--- a/build.properties
+++ b/build.properties
@@ -6,7 +6,7 @@
PROJECT: openseadragon
BUILD_MAJOR: 0
BUILD_MINOR: 9
-BUILD_ID: 37
+BUILD_ID: 40
BUILD: ${PROJECT}.${BUILD_MAJOR}.${BUILD_MINOR}.${BUILD_ID}
VERSION: ${BUILD_MAJOR}.${BUILD_MINOR}.${BUILD_ID}
diff --git a/images/next_grouphover.png b/images/next_grouphover.png
new file mode 100644
index 00000000..8d83d8a1
Binary files /dev/null and b/images/next_grouphover.png differ
diff --git a/images/next_hover.png b/images/next_hover.png
new file mode 100644
index 00000000..ba24ca98
Binary files /dev/null and b/images/next_hover.png differ
diff --git a/images/next_pressed.png b/images/next_pressed.png
new file mode 100644
index 00000000..95f169d6
Binary files /dev/null and b/images/next_pressed.png differ
diff --git a/images/next_rest.png b/images/next_rest.png
new file mode 100644
index 00000000..5ead544b
Binary files /dev/null and b/images/next_rest.png differ
diff --git a/images/previous_grouphover.png b/images/previous_grouphover.png
new file mode 100644
index 00000000..016e6395
Binary files /dev/null and b/images/previous_grouphover.png differ
diff --git a/images/previous_hover.png b/images/previous_hover.png
new file mode 100644
index 00000000..d4a5c155
Binary files /dev/null and b/images/previous_hover.png differ
diff --git a/images/previous_pressed.png b/images/previous_pressed.png
new file mode 100644
index 00000000..f999fe49
Binary files /dev/null and b/images/previous_pressed.png differ
diff --git a/images/previous_rest.png b/images/previous_rest.png
new file mode 100644
index 00000000..9716dac6
Binary files /dev/null and b/images/previous_rest.png differ
diff --git a/openseadragon.js b/openseadragon.js
index a14ef717..4f0ceedc 100644
--- a/openseadragon.js
+++ b/openseadragon.js
@@ -1,5 +1,5 @@
/**
- * @version OpenSeadragon 0.9.37
+ * @version OpenSeadragon 0.9.40
*
* @fileOverview
*
@@ -188,6 +188,12 @@
* interactions include draging the image in a plane, and zooming in toward
* and away from the image.
*
+ * @param {Boolean} [options.preserveViewport=false]
+ * If the viewer has been configured with a sequence of tile sources, then
+ * normally navigating to through each image resets the viewport to 'home'
+ * position. If preserveViewport is set to true, then the viewport position
+ * is preserved when navigating between images in the sequence.
+ *
* @param {String} [options.prefixUrl='']
* Appends the prefixUrl to navImages paths, which is very useful
* since the default paths are rarely useful for production
@@ -428,43 +434,50 @@ OpenSeadragon = window.OpenSeadragon || function( options ){
* @static
*/
DEFAULT_SETTINGS: {
- xmlPath: null,
- tileSources: null,
- debugMode: true,
- animationTime: 1.5,
- blendTime: 0.5,
- alwaysBlend: false,
- autoHideControls: true,
- immediateRender: false,
- wrapHorizontal: false,
- wrapVertical: false,
- minZoomImageRatio: 0.8,
- maxZoomPixelRatio: 2,
- visibilityRatio: 0.5,
- springStiffness: 5.0,
- imageLoaderLimit: 0,
- clickTimeThreshold: 200,
- clickDistThreshold: 5,
- zoomPerClick: 2.0,
- zoomPerScroll: 1.2,
- zoomPerSecond: 2.0,
- showNavigationControl: true,
-
- showNavigator: false,
- navigatorElement: null,
- navigatorHeight: null,
- navigatorWidth: null,
- navigatorPosition: null,
- navigatorSizeRatio: 0.25,
+ //DATA SOURCE DETAILS
+ xmlPath: null,
+ tileSources: null,
+ tileHost: null,
+
+ //INTERFACE FEATURES
+ debugMode: true,
+ animationTime: 1.5,
+ blendTime: 0.5,
+ alwaysBlend: false,
+ autoHideControls: true,
+ immediateRender: false,
+ wrapHorizontal: false,
+ wrapVertical: false,
+ panHorizontal: true,
+ panVertical: true,
+ visibilityRatio: 0.5,
+ springStiffness: 5.0,
+ clickTimeThreshold: 200,
+ clickDistThreshold: 5,
+ zoomPerClick: 2.0,
+ zoomPerScroll: 1.2,
+ zoomPerSecond: 2.0,
+ showNavigationControl: true,
+ controlsFadeDelay: 2000,
+ controlsFadeLength: 1500,
+ mouseNavEnabled: true,
+ showNavigator: false,
+ navigatorElement: null,
+ navigatorHeight: null,
+ navigatorWidth: null,
+ navigatorPosition: null,
+ navigatorSizeRatio: 0.25,
+ preserveViewport: false,
- //These two were referenced but never defined
- controlsFadeDelay: 2000,
- controlsFadeLength: 1500,
+ //PERFORMANCE SETTINGS
+ minPixelRatio: 0.5,
+ imageLoaderLimit: 0,
+ maxImageCacheCount: 200,
+ minZoomImageRatio: 0.9,
+ maxZoomPixelRatio: 2,
- maxImageCacheCount: 200,
- minPixelRatio: 0.5,
- mouseNavEnabled: true,
- prefixUrl: null,
+ //INTERFACE RESOURCE SETTINGS
+ prefixUrl: null,
navImages: {
zoomIn: {
REST: '/images/zoomin_rest.png',
@@ -489,6 +502,18 @@ OpenSeadragon = window.OpenSeadragon || function( options ){
GROUP: '/images/fullpage_grouphover.png',
HOVER: '/images/fullpage_hover.png',
DOWN: '/images/fullpage_pressed.png'
+ },
+ previous: {
+ REST: '/images/previous_rest.png',
+ GROUP: '/images/previous_grouphover.png',
+ HOVER: '/images/previous_hover.png',
+ DOWN: '/images/previous_pressed.png'
+ },
+ next: {
+ REST: '/images/next_rest.png',
+ GROUP: '/images/next_grouphover.png',
+ HOVER: '/images/next_hover.png',
+ DOWN: '/images/next_pressed.png'
}
}
},
@@ -1192,7 +1217,7 @@ OpenSeadragon = window.OpenSeadragon || function( options ){
* @param {String} xmlString
* @param {Function} callback
*/
- createFromDZI: function( dzi, callback ) {
+ createFromDZI: function( dzi, callback, tileHost ) {
var async = typeof ( callback ) == "function",
xmlUrl = dzi.substring(0,1) != '<' ? dzi : null,
xmlString = xmlUrl ? null : dzi,
@@ -1203,7 +1228,12 @@ OpenSeadragon = window.OpenSeadragon || function( options ){
tilesUrl;
- if( xmlUrl ){
+ if( tileHost ){
+
+ tilesUrl = tileHost + "/_files/";
+
+ } else if( xmlUrl ) {
+
urlParts = xmlUrl.split( '/' );
filename = urlParts[ urlParts.length - 1 ];
lastDot = filename.lastIndexOf( '.' );
@@ -1213,6 +1243,7 @@ OpenSeadragon = window.OpenSeadragon || function( options ){
}
tilesUrl = urlParts.join( '/' ) + "_files/";
+
}
function finish( func, obj ) {
@@ -2143,7 +2174,7 @@ $.EventHandler.prototype = {
function triggerOthers( tracker, handler, event ) {
var otherHash;
for ( otherHash in ACTIVE ) {
- if ( trackers.hasOwnProperty( otherHash ) && tracker.hash != otherHash ) {
+ if ( ACTIVE.hasOwnProperty( otherHash ) && tracker.hash != otherHash ) {
handler( ACTIVE[ otherHash ], event );
}
}
@@ -2156,13 +2187,16 @@ $.EventHandler.prototype = {
*/
function onFocus( tracker, event ){
//console.log( "focus %s", event );
+ var propagate;
if ( tracker.focusHandler ) {
try {
- tracker.focusHandler(
+ propagate = tracker.focusHandler(
tracker,
event
);
- $.cancelEvent( event );
+ if( propagate === false ){
+ $.cancelEvent( event );
+ }
} catch ( e ) {
$.console.error(
"%s while executing key handler: %s",
@@ -2181,13 +2215,16 @@ $.EventHandler.prototype = {
*/
function onBlur( tracker, event ){
//console.log( "blur %s", event );
+ var propagate;
if ( tracker.blurHandler ) {
try {
- tracker.blurHandler(
+ propagate = tracker.blurHandler(
tracker,
event
);
- $.cancelEvent( event );
+ if( propagate === false ){
+ $.cancelEvent( event );
+ }
} catch ( e ) {
$.console.error(
"%s while executing key handler: %s",
@@ -2214,7 +2251,7 @@ $.EventHandler.prototype = {
event.keyCode ? event.keyCode : event.charCode,
event.shiftKey
);
- if( !propagate ){
+ if( propagate === false ){
$.cancelEvent( event );
}
} catch ( e ) {
@@ -2236,7 +2273,8 @@ $.EventHandler.prototype = {
function onMouseOver( tracker, event ) {
var event = $.getEvent( event ),
- delegate = THIS[ tracker.hash ];
+ delegate = THIS[ tracker.hash ],
+ propagate;
if ( $.Browser.vendor == $.BROWSERS.IE &&
delegate.capturing &&
@@ -2262,12 +2300,15 @@ $.EventHandler.prototype = {
if ( tracker.enterHandler ) {
try {
- tracker.enterHandler(
+ propagate = tracker.enterHandler(
tracker,
getMouseRelative( event, tracker.element ),
delegate.buttonDown,
IS_BUTTON_DOWN
);
+ if( propagate === false ){
+ $.cancelEvent( event );
+ }
} catch ( e ) {
$.console.error(
"%s while executing enter handler: %s",
@@ -2286,7 +2327,8 @@ $.EventHandler.prototype = {
*/
function onMouseOut( tracker, event ) {
var event = $.getEvent( event ),
- delegate = THIS[ tracker.hash ];
+ delegate = THIS[ tracker.hash ],
+ propagate;
if ( $.Browser.vendor == $.BROWSERS.IE &&
delegate.capturing &&
@@ -2312,12 +2354,16 @@ $.EventHandler.prototype = {
if ( tracker.exitHandler ) {
try {
- tracker.exitHandler(
+ propagate = tracker.exitHandler(
tracker,
getMouseRelative( event, tracker.element ),
delegate.buttonDown,
IS_BUTTON_DOWN
);
+
+ if( propagate === false ){
+ $.cancelEvent( event );
+ }
} catch ( e ) {
$.console.error(
"%s while executing exit handler: %s",
@@ -2336,7 +2382,8 @@ $.EventHandler.prototype = {
*/
function onMouseDown( tracker, event ) {
var event = $.getEvent( event ),
- delegate = THIS[ tracker.hash ];
+ delegate = THIS[ tracker.hash ],
+ propagate;
if ( event.button == 2 ) {
return;
@@ -2350,10 +2397,13 @@ $.EventHandler.prototype = {
if ( tracker.pressHandler ) {
try {
- tracker.pressHandler(
+ propagate = tracker.pressHandler(
tracker,
getMouseRelative( event, tracker.element )
);
+ if( propagate === false ){
+ $.cancelEvent( event );
+ }
} catch (e) {
$.console.error(
"%s while executing press handler: %s",
@@ -2420,7 +2470,8 @@ $.EventHandler.prototype = {
//were we inside the tracked element when we were pressed
insideElementPress = delegate.buttonDown,
//are we still inside the tracked element when we released
- insideElementRelease = delegate.insideElement;
+ insideElementRelease = delegate.insideElement,
+ propagate;
if ( event.button == 2 ) {
return;
@@ -2430,12 +2481,15 @@ $.EventHandler.prototype = {
if ( tracker.releaseHandler ) {
try {
- tracker.releaseHandler(
+ propagate = tracker.releaseHandler(
tracker,
getMouseRelative( event, tracker.element ),
insideElementPress,
insideElementRelease
);
+ if( propagate === false ){
+ $.cancelEvent( event );
+ }
} catch (e) {
$.console.error(
"%s while executing release handler: %s",
@@ -2544,7 +2598,8 @@ $.EventHandler.prototype = {
* @inner
*/
function onMouseWheelSpin( tracker, event ) {
- var nDelta = 0;
+ var nDelta = 0,
+ propagate;
if ( !event ) { // For IE, access the global (window) event object
event = window.event;
@@ -2565,12 +2620,15 @@ $.EventHandler.prototype = {
if ( tracker.scrollHandler ) {
try {
- tracker.scrollHandler(
+ propagate = tracker.scrollHandler(
tracker,
getMouseRelative( event, tracker.element ),
nDelta,
event.shiftKey
);
+ if( propagate === false ){
+ $.cancelEvent( event );
+ }
} catch (e) {
$.console.error(
"%s while executing scroll handler: %s",
@@ -2579,8 +2637,6 @@ $.EventHandler.prototype = {
e
);
}
-
- $.cancelEvent( event );
}
};
@@ -2591,7 +2647,8 @@ $.EventHandler.prototype = {
*/
function handleMouseClick( tracker, event ) {
var event = $.getEvent( event ),
- delegate = THIS[ tracker.hash ];
+ delegate = THIS[ tracker.hash ],
+ propagate;
if ( event.button == 2 ) {
return;
@@ -2605,12 +2662,15 @@ $.EventHandler.prototype = {
if ( tracker.clickHandler ) {
try {
- tracker.clickHandler(
+ propagate = tracker.clickHandler(
tracker,
getMouseRelative( event, tracker.element ),
quick,
event.shiftKey
);
+ if( propagate === false ){
+ $.cancelEvent( event );
+ }
} catch ( e ) {
$.console.error(
"%s while executing click handler: %s",
@@ -2631,18 +2691,22 @@ $.EventHandler.prototype = {
var event = $.getEvent( event ),
delegate = THIS[ tracker.hash ],
point = getMouseAbsolute( event ),
- delta = point.minus( delegate.lastPoint );
+ delta = point.minus( delegate.lastPoint ),
+ propagate;
delegate.lastPoint = point;
if ( tracker.dragHandler ) {
try {
- tracker.dragHandler(
+ propagate = tracker.dragHandler(
tracker,
getMouseRelative( event, tracker.element ),
delta,
event.shiftKey
);
+ if( propagate === false ){
+ $.cancelEvent( event );
+ }
} catch (e) {
$.console.error(
"%s while executing drag handler: %s",
@@ -2652,7 +2716,6 @@ $.EventHandler.prototype = {
);
}
- $.cancelEvent( event );
}
};
@@ -3131,16 +3194,25 @@ $.Viewer = function( options ) {
delete options.config;
}
+ //Public properties
//Allow the options object to override global defaults
$.extend( true, this, {
- id: options.id,
- hash: options.id,
- overlays: [],
- overlayControls: [],
+ //internal state and dom identifiers
+ id: options.id,
+ hash: options.id,
+
+ //dom nodes
+ element: null,
+ canvas: null,
+ container: null,
+
+ //TODO: not sure how to best describe these
+ overlays: [],
+ overlayControls:[],
//private state properties
- previousBody: [],
+ previousBody: [],
//This was originally initialized in the constructor and so could never
//have anything in it. now it can because we allow it to be specified
@@ -3156,13 +3228,76 @@ $.Viewer = function( options ) {
drawer: null,
viewport: null,
navigator: null,
+
+ //UI image resources
+ //TODO: rename navImages to uiImages
+ navImages: null,
+
+ //interface button controls
+ buttons: null,
+
+ //TODO: this is defunct so safely remove it
profiler: null
}, $.DEFAULT_SETTINGS, options );
+ //Private state properties
+ THIS[ this.hash ] = {
+ "fsBoundsDelta": new $.Point( 1, 1 ),
+ "prevContainerSize": null,
+ "lastOpenStartTime": 0,
+ "lastOpenEndTime": 0,
+ "animating": false,
+ "forceRedraw": false,
+ "mouseInside": false,
+ "group": null,
+ // whether we should be continuously zooming
+ "zooming": false,
+ // how much we should be continuously zooming by
+ "zoomFactor": null,
+ "lastZoomTime": null,
+ // did we decide this viewer has a sequence of tile sources
+ "sequenced": false,
+ "sequence": 0
+ };
+
+ //Inherit some behaviors and properties
$.EventHandler.call( this );
$.ControlDock.call( this, options );
+ //Deal with tile sources
+ var initialTileSource,
+ customTileSource;
+
+ if ( this.xmlPath ){
+ //Deprecated option. Now it is preferred to use the tileSources option
+ this.tileSources = [ this.xmlPath ];
+ }
+
+ if ( this.tileSources ){
+ //tileSources is a complex option...
+ //It can be a string, object, function, or an array of any of these.
+ // - A String implies a DZI
+ // - An Srray of Objects implies a simple image
+ // - A Function implies a custom tile source callback
+ // - An Array that is not an Array of simple Objects implies a sequence
+ // of tile sources which can be any of the above
+ if( $.isArray( this.tileSources ) ){
+ if( $.isPlainObject( this.tileSources[ 0 ] ) ){
+ //This is a non-sequenced legacy tile source
+ initialTileSource = this.tileSources;
+ } else {
+ //Sequenced tile source
+ initialTileSource = this.tileSources[ 0 ];
+ THIS[ this.hash ].sequenced = true;
+ }
+ } else {
+ initialTileSource = this.tileSources;
+ }
+
+ this.openTileSource( initialTileSource );
+ }
+
this.element = this.element || document.getElementById( this.id );
this.canvas = $.makeNeutralElement( "div" );
@@ -3184,7 +3319,7 @@ $.Viewer = function( options ) {
container.textAlign = "left"; // needed to protect against
}( this.container.style ));
- this.container.insertBefore( this.canvas, this.container.firstChild);
+ this.container.insertBefore( this.canvas, this.container.firstChild );
this.element.appendChild( this.container );
//Used for toggling between fullscreen and default container size
@@ -3195,16 +3330,6 @@ $.Viewer = function( options ) {
this.bodyOverflow = document.body.style.overflow;
this.docOverflow = document.documentElement.style.overflow;
- THIS[ this.hash ] = {
- "fsBoundsDelta": new $.Point( 1, 1 ),
- "prevContainerSize": null,
- "lastOpenStartTime": 0,
- "lastOpenEndTime": 0,
- "animating": false,
- "forceRedraw": false,
- "mouseInside": false
- };
-
this.innerTracker = new $.MouseTracker({
element: this.canvas,
clickTimeThreshold: this.clickTimeThreshold,
@@ -3225,16 +3350,6 @@ $.Viewer = function( options ) {
}).setTracking( this.mouseNavEnabled ? true : false ); // always tracking
- //private state properties
- $.extend( THIS[ this.hash ], {
- "group": null,
- // whether we should be continuously zooming
- "zooming": false,
- // how much we should be continuously zooming by
- "zoomFactor": null,
- "lastZoomTime": null
- });
-
//////////////////////////////////////////////////////////////////////////
// Navigation Controls
//////////////////////////////////////////////////////////////////////////
@@ -3247,16 +3362,15 @@ $.Viewer = function( options ) {
onFullPageHandler = $.delegate( this, onFullPage ),
onFocusHandler = $.delegate( this, onFocus ),
onBlurHandler = $.delegate( this, onBlur ),
- navImages = this.navImages;
+ onNextHandler = $.delegate( this, onNext ),
+ onPreviousHandler = $.delegate( this, onPrevious ),
+ navImages = this.navImages,
+ buttons = [];
- this.zoomInButton = null;
- this.zoomOutButton = null;
- this.goHomeButton = null;
- this.fullPageButton = null;
if( this.showNavigationControl ){
- this.zoomInButton = new $.Button({
+ buttons.push( this.zoomInButton = new $.Button({
clickTimeThreshold: this.clickTimeThreshold,
clickDistThreshold: this.clickDistThreshold,
tooltip: $.getString( "Tooltips.ZoomIn" ),
@@ -3271,9 +3385,9 @@ $.Viewer = function( options ) {
onExit: endZoomingHandler,
onFocus: onFocusHandler,
onBlur: onBlurHandler
- });
+ }));
- this.zoomOutButton = new $.Button({
+ buttons.push( this.zoomOutButton = new $.Button({
clickTimeThreshold: this.clickTimeThreshold,
clickDistThreshold: this.clickDistThreshold,
tooltip: $.getString( "Tooltips.ZoomOut" ),
@@ -3288,9 +3402,9 @@ $.Viewer = function( options ) {
onExit: endZoomingHandler,
onFocus: onFocusHandler,
onBlur: onBlurHandler
- });
+ }));
- this.goHomeButton = new $.Button({
+ buttons.push( this.homeButton = new $.Button({
clickTimeThreshold: this.clickTimeThreshold,
clickDistThreshold: this.clickDistThreshold,
tooltip: $.getString( "Tooltips.Home" ),
@@ -3301,9 +3415,9 @@ $.Viewer = function( options ) {
onRelease: onHomeHandler,
onFocus: onFocusHandler,
onBlur: onBlurHandler
- });
+ }));
- this.fullPageButton = new $.Button({
+ buttons.push( this.fullPageButton = new $.Button({
clickTimeThreshold: this.clickTimeThreshold,
clickDistThreshold: this.clickDistThreshold,
tooltip: $.getString( "Tooltips.FullPage" ),
@@ -3314,17 +3428,44 @@ $.Viewer = function( options ) {
onRelease: onFullPageHandler,
onFocus: onFocusHandler,
onBlur: onBlurHandler
- });
+ }));
- this.buttons = new $.ButtonGroup({
+ if( THIS[ this.hash ].sequenced ){
+
+ buttons.push( this.previousButton = new $.Button({
+ clickTimeThreshold: this.clickTimeThreshold,
+ clickDistThreshold: this.clickDistThreshold,
+ tooltip: $.getString( "Tooltips.PreviousPage" ),
+ srcRest: resolveUrl( this.prefixUrl, navImages.previous.REST ),
+ srcGroup: resolveUrl( this.prefixUrl, navImages.previous.GROUP ),
+ srcHover: resolveUrl( this.prefixUrl, navImages.previous.HOVER ),
+ srcDown: resolveUrl( this.prefixUrl, navImages.previous.DOWN ),
+ onRelease: onPreviousHandler,
+ onFocus: onFocusHandler,
+ onBlur: onBlurHandler
+ }));
+
+ buttons.push( this.nextButton = new $.Button({
+ clickTimeThreshold: this.clickTimeThreshold,
+ clickDistThreshold: this.clickDistThreshold,
+ tooltip: $.getString( "Tooltips.NextPage" ),
+ srcRest: resolveUrl( this.prefixUrl, navImages.next.REST ),
+ srcGroup: resolveUrl( this.prefixUrl, navImages.next.GROUP ),
+ srcHover: resolveUrl( this.prefixUrl, navImages.next.HOVER ),
+ srcDown: resolveUrl( this.prefixUrl, navImages.next.DOWN ),
+ onRelease: onNextHandler,
+ onFocus: onFocusHandler,
+ onBlur: onBlurHandler
+ }));
+
+ this.previousButton.disable();
+
+ }
+
+ this.buttons = new $.ButtonGroup({
+ buttons: buttons,
clickTimeThreshold: this.clickTimeThreshold,
- clickDistThreshold: this.clickDistThreshold,
- buttons: [
- this.zoomInButton,
- this.zoomOutButton,
- this.goHomeButton,
- this.fullPageButton
- ]
+ clickDistThreshold: this.clickDistThreshold
});
this.navControl = this.buttons.element;
@@ -3346,73 +3487,10 @@ $.Viewer = function( options ) {
);
}
- //Instantiate a navigator if configured
- if ( this.showNavigator ){
- this.navigator = new $.Navigator({
- id: this.navigatorElement,
- position: this.navigatorPosition,
- sizeRatio: this.navigatorSizeRatio,
- height: this.navigatorHeight,
- width: this.navigatorWidth,
- tileSources: this.tileSources,
- prefixUrl: this.prefixUrl,
- overlays: this.overlays,
- viewer: this
- });
- }
-
window.setTimeout( function(){
beginControlsAutoHide( _this );
}, 1 ); // initial fade out
- var initialTileSource,
- customTileSource;
-
- if ( this.xmlPath ){
- //Deprecated option. Now it is preferred to use the tileSources option
- this.tileSources = [ this.xmlPath ];
- }
-
- if ( this.tileSources ){
- //tileSource is a complex option...
- //It can be a string, object, function, or an array of any of these.
- //A string implies a DZI
- //An object implies a simple image
- //A function implies a custom tile source callback
- //An array implies a sequence of tile sources which can be any of the
- //above
- if( $.isArray( this.tileSources ) ){
- if( $.isPlainObject( this.tileSources[ 0 ] ) ){
- //This is a non-sequenced legacy tile source
- initialTileSource = this.tileSources;
- } else {
- //Sequenced tile source
- initialTileSource = this.tileSources[ 0 ];
- }
- } else {
- initialTileSource = this.tileSources
- }
-
- if ( $.type( initialTileSource ) == 'string') {
- //Standard DZI format
- this.openDzi( initialTileSource );
- } else if ( $.isArray( initialTileSource ) ){
- //Legacy image pyramid
- this.open( new $.LegacyTileSource( initialTileSource ) );
- } else if ( $.isPlainObject( initialTileSource ) && $.isFunction( initialTileSource.getTileUrl ) ){
- //Custom tile source
- customTileSource = new $.TileSource(
- initialTileSource.width,
- initialTileSource.height,
- initialTileSource.tileSize,
- initialTileSource.tileOverlap,
- initialTileSource.minLevel,
- initialTileSource.maxLevel
- );
- customTileSource.getTileUrl = initialTileSource.getTileUrl;
- this.open( customTileSource );
- }
- }
};
$.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype, {
@@ -3442,7 +3520,8 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
dzi,
function( source ){
_this.open( source );
- }
+ },
+ this.tileHost
);
return this;
},
@@ -3453,10 +3532,34 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
* @return {OpenSeadragon.Viewer} Chainable.
*/
openTileSource: function ( tileSource ) {
- var _this = this;
- window.setTimeout( function () {
- _this.open( tileSource );
- }, 1 );
+ var _this = this,
+ customTileSource;
+
+ setTimeout(function(){
+ if ( $.type( tileSource ) == 'string') {
+ //Standard DZI format
+ _this.openDzi( tileSource );
+ } else if ( $.isArray( tileSource ) ){
+ //Legacy image pyramid
+ _this.open( new $.LegacyTileSource( tileSource ) );
+ } else if ( $.isPlainObject( tileSource ) && $.isFunction( tileSource.getTileUrl ) ){
+ //Custom tile source
+ customTileSource = new $.TileSource(
+ tileSource.width,
+ tileSource.height,
+ tileSource.tileSize,
+ tileSource.tileOverlap,
+ tileSource.minLevel,
+ tileSource.maxLevel
+ );
+ customTileSource.getTileUrl = tileSource.getTileUrl;
+ _this.open( customTileSource );
+ } else {
+ //can assume it's already a tile source implementation
+ _this.open( tileSource );
+ }
+ }, 1);
+
return this;
},
@@ -3471,7 +3574,7 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
i;
if ( this.source ) {
- this.close();
+ this.close( );
}
// to ignore earlier opens
@@ -3491,7 +3594,7 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
this.source = source;
}
- this.viewport = new $.Viewport({
+ this.viewport = this.viewport ? this.viewport : new $.Viewport({
containerSize: THIS[ this.hash ].prevContainerSize,
contentSize: this.source.dimensions,
springStiffness: this.springStiffness,
@@ -3502,6 +3605,9 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
wrapHorizontal: this.wrapHorizontal,
wrapVertical: this.wrapVertical
});
+ if( this.preserveVewport ){
+ this.viewport.resetContentSize( this.source.dimensions );
+ }
this.drawer = new $.Drawer({
source: this.source,
@@ -3519,6 +3625,22 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
minPixelRatio: this.minPixelRatio
});
+ //Instantiate a navigator if configured
+ if ( this.showNavigator && ! this.navigator ){
+ this.navigator = new $.Navigator({
+ id: this.navigatorElement,
+ position: this.navigatorPosition,
+ sizeRatio: this.navigatorSizeRatio,
+ height: this.navigatorHeight,
+ width: this.navigatorWidth,
+ tileSources: this.tileSources,
+ tileHost: this.tileHost,
+ prefixUrl: this.prefixUrl,
+ overlays: this.overlays,
+ viewer: this
+ });
+ }
+
//this.profiler = new $.Profiler();
THIS[ this.hash ].animating = false;
@@ -3559,6 +3681,11 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
}
VIEWERS[ this.hash ] = this;
this.raiseEvent( "open" );
+
+ if( this.navigator ){
+ this.navigator.open( source );
+ }
+
return this;
},
@@ -3567,10 +3694,10 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
* @name OpenSeadragon.Viewer.prototype.close
* @return {OpenSeadragon.Viewer} Chainable.
*/
- close: function () {
+ close: function ( ) {
this.source = null;
- this.viewport = null;
this.drawer = null;
+ this.viewport = this.preserveViewport ? this.viewport : null;
//this.profiler = null;
this.canvas.innerHTML = "";
@@ -3794,6 +3921,9 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
viewer = VIEWERS[ hash ];
if( viewer !== this && viewer != this.navigator ){
viewer.open( viewer.source );
+ if( viewer.navigator ){
+ viewer.navigator.open( viewer.source );
+ }
}
}
}
@@ -3829,10 +3959,11 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
});
+
+
///////////////////////////////////////////////////////////////////////////////
// Schedulers provide the general engine for animation
///////////////////////////////////////////////////////////////////////////////
-
function scheduleUpdate( viewer, updateFunc, prevUpdateTime ){
var currentTime,
targetTime,
@@ -3855,6 +3986,7 @@ function scheduleUpdate( viewer, updateFunc, prevUpdateTime ){
}, deltaTime );
};
+
//provides a sequence in the fade animation
function scheduleControlsFade( viewer ) {
window.setTimeout( function(){
@@ -3862,6 +3994,7 @@ function scheduleControlsFade( viewer ) {
}, 20);
};
+
//initiates an animation to hide the controls
function beginControlsAutoHide( viewer ) {
if ( !viewer.autoHideControls ) {
@@ -3903,6 +4036,7 @@ function updateControlsFade( viewer ) {
}
};
+
//stop the fade animation on the controls and show them
function abortControlsAutoHide( viewer ) {
var i;
@@ -3913,6 +4047,7 @@ function abortControlsAutoHide( viewer ) {
};
+
///////////////////////////////////////////////////////////////////////////////
// Default view event handlers.
///////////////////////////////////////////////////////////////////////////////
@@ -3941,6 +4076,12 @@ function onCanvasClick( tracker, position, quick, shift ) {
function onCanvasDrag( tracker, position, delta, shift ) {
if ( this.viewport ) {
+ if( !this.panHorizontal ){
+ delta.x = 0;
+ }
+ if( !this.panVertical ){
+ delta.y = 0;
+ }
this.viewport.panBy(
this.viewport.deltaPointsFromPixels(
delta.negate()
@@ -3965,6 +4106,8 @@ function onCanvasScroll( tracker, position, scroll, shift ) {
);
this.viewport.applyConstraints();
}
+ //cancels event
+ return false;
};
function onContainerExit( tracker, position, buttonDownElement, buttonDownAny ) {
@@ -4060,36 +4203,42 @@ function updateOnce( viewer ) {
//viewer.profiler.endUpdate();
};
+
+
///////////////////////////////////////////////////////////////////////////////
// Navigation Controls
///////////////////////////////////////////////////////////////////////////////
-
function resolveUrl( prefix, url ) {
return prefix ? prefix + url : url;
};
+
function beginZoomingIn() {
THIS[ this.hash ].lastZoomTime = +new Date();
THIS[ this.hash ].zoomFactor = this.zoomPerSecond;
THIS[ this.hash ].zooming = true;
scheduleZoom( this );
-}
+};
+
function beginZoomingOut() {
THIS[ this.hash ].lastZoomTime = +new Date();
THIS[ this.hash ].zoomFactor = 1.0 / this.zoomPerSecond;
THIS[ this.hash ].zooming = true;
scheduleZoom( this );
-}
+};
+
function endZooming() {
THIS[ this.hash ].zooming = false;
-}
+};
+
function scheduleZoom( viewer ) {
window.setTimeout( $.delegate( viewer, doZoom ), 10 );
-}
+};
+
function doZoom() {
var currentTime,
@@ -4108,6 +4257,7 @@ function doZoom() {
}
};
+
function doSingleZoomIn() {
if ( this.viewport ) {
THIS[ this.hash ].zooming = false;
@@ -4118,6 +4268,7 @@ function doSingleZoomIn() {
}
};
+
function doSingleZoomOut() {
if ( this.viewport ) {
THIS[ this.hash ].zooming = false;
@@ -4128,17 +4279,20 @@ function doSingleZoomOut() {
}
};
+
function lightUp() {
this.buttons.emulateEnter();
this.buttons.emulateExit();
};
+
function onHome() {
if ( this.viewport ) {
this.viewport.goHome();
}
};
+
function onFullPage() {
this.setFullPage( !this.isFullPage() );
// correct for no mouseout event on change
@@ -4149,6 +4303,49 @@ function onFullPage() {
}
};
+
+function onPrevious(){
+ var previous = THIS[ this.hash ].sequence - 1,
+ preserveVewport = true;
+ if( previous >= 0 ){
+
+ THIS[ this.hash ].sequence = previous;
+
+ if( 0 === previous ){
+ //Disable previous button
+ this.previousButton.disable();
+ }
+ if( this.tileSources.length > 0 ){
+ //Enable next button
+ this.nextButton.enable();
+ }
+
+ this.openTileSource( this.tileSources[ previous ] );
+ }
+};
+
+
+function onNext(){
+ var next = THIS[ this.hash ].sequence + 1,
+ preserveVewport = true;
+ if( this.tileSources.length > next ){
+
+ THIS[ this.hash ].sequence = next;
+
+ if( ( this.tileSources.length - 1 ) === next ){
+ //Disable next button
+ this.nextButton.disable();
+ }
+ if( next > 0 ){
+ //Enable previous button
+ this.previousButton.enable();
+ }
+
+ this.openTileSource( this.tileSources[ next ] );
+ }
+};
+
+
}( OpenSeadragon ));
(function( $ ){
@@ -4215,13 +4412,31 @@ $.Navigator = function( options ){
style.cssFloat = 'left'; //Firefox
style.styleFloat = 'left'; //IE
style.zIndex = 999999999;
+ style.cursor = 'default';
}( this.displayRegion.style ));
+ this.element.innerTracker = new $.MouseTracker({
+ element: this.element,
+ scrollHandler: function(){
+ //dont scroll the page up and down if the user is scrolling
+ //in the navigator
+ return false;
+ }
+ }).setTracking( true );
+
this.displayRegion.innerTracker = new $.MouseTracker({
element: this.displayRegion,
clickTimeThreshold: this.clickTimeThreshold,
clickDistThreshold: this.clickDistThreshold,
+ clickHandler: $.delegate( this, onCanvasClick ),
+ dragHandler: $.delegate( this, onCanvasDrag ),
+ releaseHandler: $.delegate( this, onCanvasRelease ),
+ scrollHandler: $.delegate( this, onCanvasScroll ),
focusHandler: function(){
+ var point = $.getElementPosition( _this.viewer.element );
+
+ window.scrollTo( 0, point.y );
+
_this.viewer.setControlsEnabled( true );
(function( style ){
style.border = '2px solid #437AB2';
@@ -4241,12 +4456,15 @@ $.Navigator = function( options ){
switch( keyCode ){
case 61://=|+
_this.viewer.viewport.zoomBy(1.1);
+ _this.viewer.viewport.applyConstraints();
return false;
case 45://-|_
_this.viewer.viewport.zoomBy(0.9);
+ _this.viewer.viewport.applyConstraints();
return false;
case 48://0|)
_this.viewer.viewport.goHome();
+ _this.viewer.viewport.applyConstraints();
return false;
case 119://w
case 87://W
@@ -4254,6 +4472,7 @@ $.Navigator = function( options ){
shiftKey ?
_this.viewer.viewport.zoomBy(1.1):
_this.viewer.viewport.panBy(new $.Point(0, -0.05));
+ _this.viewer.viewport.applyConstraints();
return false;
case 115://s
case 83://S
@@ -4261,14 +4480,17 @@ $.Navigator = function( options ){
shiftKey ?
_this.viewer.viewport.zoomBy(0.9):
_this.viewer.viewport.panBy(new $.Point(0, 0.05));
+ _this.viewer.viewport.applyConstraints();
return false;
case 97://a
case 37://left arrow
_this.viewer.viewport.panBy(new $.Point(-0.05, 0));
+ _this.viewer.viewport.applyConstraints();
return false;
case 100://d
case 39://right arrow
_this.viewer.viewport.panBy(new $.Point(0.05, 0));
+ _this.viewer.viewport.applyConstraints();
return false;
default:
//console.log( 'navigator keycode %s', keyCode );
@@ -4309,25 +4531,75 @@ $.extend( $.Navigator.prototype, $.EventHandler.prototype, $.Viewer.prototype, {
update: function( viewport ){
- var bounds = viewport.getBounds( true ),
- topleft = this.viewport.pixelFromPoint( bounds.getTopLeft() )
+ var bounds,
+ topleft,
+ bottomright;
+
+ if( viewport && this.viewport ){
+ bounds = viewport.getBounds( true );
+ topleft = this.viewport.pixelFromPoint( bounds.getTopLeft() );
bottomright = this.viewport.pixelFromPoint( bounds.getBottomRight() );
- //update style for navigator-box
- (function(style){
+ //update style for navigator-box
+ (function(style){
- style.top = topleft.y + 'px';
- style.left = topleft.x + 'px';
- style.width = ( Math.abs( topleft.x - bottomright.x ) - 3 ) + 'px';
- style.height = ( Math.abs( topleft.y - bottomright.y ) - 3 ) + 'px';
+ style.top = topleft.y + 'px';
+ style.left = topleft.x + 'px';
+ style.width = ( Math.abs( topleft.x - bottomright.x ) - 3 ) + 'px';
+ style.height = ( Math.abs( topleft.y - bottomright.y ) - 3 ) + 'px';
- }( this.displayRegion.style ));
+ }( this.displayRegion.style ));
+ }
}
});
+function onCanvasClick( tracker, position, quick, shift ) {
+ this.displayRegion.focus();
+};
+
+
+function onCanvasDrag( tracker, position, delta, shift ) {
+ if ( this.viewer.viewport ) {
+ if( !this.panHorizontal ){
+ delta.x = 0;
+ }
+ if( !this.panVertical ){
+ delta.y = 0;
+ }
+ this.viewer.viewport.panBy(
+ this.viewport.deltaPointsFromPixels(
+ delta
+ )
+ );
+ }
+};
+
+
+function onCanvasRelease( tracker, position, insideElementPress, insideElementRelease ) {
+ if ( insideElementPress && this.viewer.viewport ) {
+ this.viewer.viewport.applyConstraints();
+ }
+};
+
+function onCanvasScroll( tracker, position, scroll, shift ) {
+ var factor;
+ if ( this.viewer.viewport ) {
+ factor = Math.pow( this.zoomPerScroll, scroll );
+ this.viewer.viewport.zoomBy(
+ factor,
+ //this.viewport.pointFromPixel( position, true )
+ this.viewport.getCenter()
+ );
+ this.viewer.viewport.applyConstraints();
+ }
+ //cancels event
+ return false;
+};
+
+
}( OpenSeadragon ));
(function( $ ){
@@ -4336,28 +4608,30 @@ $.extend( $.Navigator.prototype, $.EventHandler.prototype, $.Viewer.prototype, {
// pythons gettext might be a reasonable approach.
var I18N = {
Errors: {
- Failure: "Sorry, but Seadragon Ajax can't run on your browser!\n" +
- "Please try using IE 7 or Firefox 3.\n",
- Dzc: "Sorry, we don't support Deep Zoom Collections!",
- Dzi: "Hmm, this doesn't appear to be a valid Deep Zoom Image.",
- Xml: "Hmm, this doesn't appear to be a valid Deep Zoom Image.",
- Empty: "You asked us to open nothing, so we did just that.",
- ImageFormat: "Sorry, we don't support {0}-based Deep Zoom Images.",
- Security: "It looks like a security restriction stopped us from " +
- "loading this Deep Zoom Image.",
- Status: "This space unintentionally left blank ({0} {1}).",
- Unknown: "Whoops, something inexplicably went wrong. Sorry!"
+ Failure: "Sorry, but Seadragon Ajax can't run on your browser!\n" +
+ "Please try using IE 7 or Firefox 3.\n",
+ Dzc: "Sorry, we don't support Deep Zoom Collections!",
+ Dzi: "Hmm, this doesn't appear to be a valid Deep Zoom Image.",
+ Xml: "Hmm, this doesn't appear to be a valid Deep Zoom Image.",
+ Empty: "You asked us to open nothing, so we did just that.",
+ ImageFormat: "Sorry, we don't support {0}-based Deep Zoom Images.",
+ Security: "It looks like a security restriction stopped us from " +
+ "loading this Deep Zoom Image.",
+ Status: "This space unintentionally left blank ({0} {1}).",
+ Unknown: "Whoops, something inexplicably went wrong. Sorry!"
},
Messages: {
- Loading: "Loading..."
+ Loading: "Loading..."
},
Tooltips: {
- FullPage: "Toggle full page",
- Home: "Go home",
- ZoomIn: "Zoom in",
- ZoomOut: "Zoom out"
+ FullPage: "Toggle full page",
+ Home: "Go home",
+ ZoomIn: "Zoom in",
+ ZoomOut: "Zoom out",
+ NextPage: "Next page",
+ PreviousPage: "Previous page"
}
};
@@ -4371,12 +4645,13 @@ $.extend( $, {
getString: function( prop ) {
var props = prop.split('.'),
- string = I18N,
+ string = null,
args = arguments,
i;
for ( i = 0; i < props.length; i++ ) {
- string = string[ props[ i ] ] || {}; // in case not a subproperty
+ // in case not a subproperty
+ string = I18N[ props[ i ] ] || {};
}
if ( typeof( string ) != "string" ) {
@@ -4848,8 +5123,8 @@ $.LegacyTileSource.prototype = {
var levelScale = NaN;
if ( level >= this.minLevel && level <= this.maxLevel ){
levelScale =
- this.files[ level ].height /
- this.files[ this.maxLevel ].height;
+ this.files[ level ].width /
+ this.files[ this.maxLevel ].width;
}
return levelScale;
},
@@ -4900,9 +5175,9 @@ $.LegacyTileSource.prototype = {
py = ( y === 0 ) ? 0 : this.files[ level ].height,
sx = this.files[ level ].width,
sy = this.files[ level ].height,
- scale = Math.max(
- 1.0 / dimensionsScaled.x,
- 1.0 / dimensionsScaled.y
+ scale = 1.0 / ( this.width >= this.height ?
+ dimensionsScaled.y :
+ dimensionsScaled.x
);
sx = Math.min( sx, dimensionsScaled.x - px );
@@ -5173,6 +5448,18 @@ $.extend( $.Button.prototype, $.EventHandler.prototype, {
*/
notifyGroupExit: function() {
outTo( this, $.ButtonState.REST );
+ },
+
+ disable: function(){
+ this.notifyGroupExit();
+ this.element.disabled = true;
+ $.setElementOpacity( this.element, 0.2, true );
+ },
+
+ enable: function(){
+ this.element.disabled = false;
+ $.setElementOpacity( this.element, 1.0, true );
+ this.notifyGroupEnter();
}
});
@@ -5218,6 +5505,11 @@ function stopFading( button ) {
};
function inTo( button, newState ) {
+
+ if( button.element.disabled ){
+ return;
+ }
+
if ( newState >= $.ButtonState.GROUP &&
button.currentState == $.ButtonState.REST ) {
stopFading( button );
@@ -5239,6 +5531,11 @@ function inTo( button, newState ) {
function outTo( button, newState ) {
+
+ if( button.element.disabled ){
+ return;
+ }
+
if ( newState <= $.ButtonState.HOVER &&
button.currentState == $.ButtonState.DOWN ) {
button.imgDown.style.visibility = "hidden";
@@ -5678,7 +5975,7 @@ $.Tile = function(level, x, y, bounds, exists, url) {
this.loading = false;
this.element = null;
- this.image = null;
+ this.image = null;
this.style = null;
this.position = null;
@@ -5714,7 +6011,7 @@ $.Tile.prototype = {
var position = this.position.apply( Math.floor ),
size = this.size.apply( Math.ceil );
- if ( !this.loaded ) {
+ if ( !this.loaded || !this.image ) {
$.console.warn(
"Attempting to draw tile %s when it's not yet loaded.",
this.toString()
@@ -5723,9 +6020,9 @@ $.Tile.prototype = {
}
if ( !this.element ) {
- this.element = $.makeNeutralElement("img");
- this.element.src = this.url;
- this.style = this.element.style;
+ this.element = $.makeNeutralElement("img");
+ this.element.src = this.url;
+ this.style = this.element.style;
this.style.position = "absolute";
this.style.msInterpolationMode = "nearest-neighbor";
@@ -5755,14 +6052,13 @@ $.Tile.prototype = {
var position = this.position,
size = this.size;
- if ( !this.loaded ) {
+ if ( !this.loaded || !this.image ) {
$.console.warn(
"Attempting to draw tile %s when it's not yet loaded.",
this.toString()
);
return;
}
-
context.globalAlpha = this.opacity;
context.drawImage( this.image, position.x, position.y, size.x, size.y );
},
@@ -6958,9 +7254,6 @@ $.Viewport = function( options ) {
}, options );
-
- this.contentAspect = this.contentSize.x / this.contentSize.y;
- this.contentHeight = this.contentSize.y / this.contentSize.x;
this.centerSpringX = new $.Spring({
initial: 0,
springStiffness: this.springStiffness,
@@ -6976,19 +7269,39 @@ $.Viewport = function( options ) {
springStiffness: this.springStiffness,
animationTime: this.animationTime
});
- this.homeBounds = new $.Rect( 0, 0, 1, this.contentHeight );
+ this.resetContentSize( this.contentSize );
this.goHome( true );
+ //this.fitHorizontally( true );
this.update();
};
$.Viewport.prototype = {
+ resetContentSize: function( contentSize ){
+ this.contentSize = contentSize;
+ this.contentAspectX = this.contentSize.x / this.contentSize.y;
+ this.contentAspectY = this.contentSize.y / this.contentSize.x;
+ this.homeBounds = new $.Rect(
+ 0,
+ 0,
+ 1,
+ this.contentAspectY
+ );
+ this.fitWidthBounds = new $.Rect( 0, 0, 1, this.contentAspectX );
+ this.fitHeightBounds = new $.Rect( 0, 0, 1, this.contentAspectY );
+ },
+
/**
* @function
*/
getHomeZoom: function() {
- var aspectFactor = this.contentAspect / this.getAspectRatio();
+
+ var aspectFactor = Math.min(
+ this.contentAspectX,
+ this.contentAspectY
+ ) / this.getAspectRatio();
+
return ( aspectFactor >= 1 ) ?
1 :
aspectFactor;
@@ -7141,7 +7454,7 @@ $.Viewport.prototype = {
left = bounds.x + bounds.width;
right = 1 - bounds.x;
top = bounds.y + bounds.height;
- bottom = this.contentHeight - bounds.y;
+ bottom = this.contentAspectY - bounds.y;
if ( this.wrapHorizontal ) {
//do nothing
@@ -7223,15 +7536,22 @@ $.Viewport.prototype = {
this.containerSize.x / newBounds.width
);
-
this.zoomTo( newZoom, referencePoint, immediately );
},
+
+ /**
+ * @function
+ * @param {Boolean} immediately
+ */
+ goHome: function( immediately ) {
+ return this.fitVertically( immediately );
+ },
/**
* @function
* @param {Boolean} immediately
*/
- goHome: function( immediately ) {
+ fitVertically: function( immediately ) {
var center = this.getCenter();
if ( this.wrapHorizontal ) {
@@ -7242,8 +7562,8 @@ $.Viewport.prototype = {
if ( this.wrapVertical ) {
center.y = (
- this.contentHeight + ( center.y % this.contentHeight )
- ) % this.contentHeight;
+ this.contentAspectY + ( center.y % this.contentAspectY )
+ ) % this.contentAspectY;
this.centerSpringY.resetTo( center.y );
this.centerSpringY.update();
}
@@ -7251,6 +7571,31 @@ $.Viewport.prototype = {
this.fitBounds( this.homeBounds, immediately );
},
+ /**
+ * @function
+ * @param {Boolean} immediately
+ */
+ fitHorizontally: function( immediately ) {
+ var center = this.getCenter();
+
+ if ( this.wrapHorizontal ) {
+ center.x = (
+ this.contentAspectX + ( center.x % this.contentAspectX )
+ ) % this.contentAspectX;
+ this.centerSpringX.resetTo( center.x );
+ this.centerSpringX.update();
+ }
+
+ if ( this.wrapVertical ) {
+ center.y = ( 1 + ( center.y % 1 ) ) % 1;
+ this.centerSpringY.resetTo( center.y );
+ this.centerSpringY.update();
+ }
+
+ this.fitBounds( this.fitWidthBounds, immediately );
+ },
+
+
/**
* @function
* @param {OpenSeadragon.Point} delta
diff --git a/src/button.js b/src/button.js
index d1220332..ec3217e6 100644
--- a/src/button.js
+++ b/src/button.js
@@ -223,6 +223,18 @@ $.extend( $.Button.prototype, $.EventHandler.prototype, {
*/
notifyGroupExit: function() {
outTo( this, $.ButtonState.REST );
+ },
+
+ disable: function(){
+ this.notifyGroupExit();
+ this.element.disabled = true;
+ $.setElementOpacity( this.element, 0.2, true );
+ },
+
+ enable: function(){
+ this.element.disabled = false;
+ $.setElementOpacity( this.element, 1.0, true );
+ this.notifyGroupEnter();
}
});
@@ -268,6 +280,11 @@ function stopFading( button ) {
};
function inTo( button, newState ) {
+
+ if( button.element.disabled ){
+ return;
+ }
+
if ( newState >= $.ButtonState.GROUP &&
button.currentState == $.ButtonState.REST ) {
stopFading( button );
@@ -289,6 +306,11 @@ function inTo( button, newState ) {
function outTo( button, newState ) {
+
+ if( button.element.disabled ){
+ return;
+ }
+
if ( newState <= $.ButtonState.HOVER &&
button.currentState == $.ButtonState.DOWN ) {
button.imgDown.style.visibility = "hidden";
diff --git a/src/drawer.js b/src/drawer.js
index 1d13bad2..4c629e22 100644
--- a/src/drawer.js
+++ b/src/drawer.js
@@ -10,7 +10,8 @@ var TIMEOUT = 5000,
( BROWSER == $.BROWSERS.FIREFOX ) ||
( BROWSER == $.BROWSERS.OPERA ) ||
( BROWSER == $.BROWSERS.SAFARI && BROWSER_VERSION >= 4 ) ||
- ( BROWSER == $.BROWSERS.CHROME && BROWSER_VERSION >= 2 )
+ ( BROWSER == $.BROWSERS.CHROME && BROWSER_VERSION >= 2 ) ||
+ ( BROWSER == $.BROWSERS.IE && BROWSER_VERSION >= 9 )
) && ( !navigator.appVersion.match( 'Mobile' ) ),
USE_CANVAS = $.isFunction( document.createElement( "canvas" ).getContext ) &&
diff --git a/src/legacytilesource.js b/src/legacytilesource.js
index ea668d75..b551169e 100644
--- a/src/legacytilesource.js
+++ b/src/legacytilesource.js
@@ -49,8 +49,8 @@ $.LegacyTileSource.prototype = {
var levelScale = NaN;
if ( level >= this.minLevel && level <= this.maxLevel ){
levelScale =
- this.files[ level ].height /
- this.files[ this.maxLevel ].height;
+ this.files[ level ].width /
+ this.files[ this.maxLevel ].width;
}
return levelScale;
},
@@ -101,9 +101,9 @@ $.LegacyTileSource.prototype = {
py = ( y === 0 ) ? 0 : this.files[ level ].height,
sx = this.files[ level ].width,
sy = this.files[ level ].height,
- scale = Math.max(
- 1.0 / dimensionsScaled.x,
- 1.0 / dimensionsScaled.y
+ scale = 1.0 / ( this.width >= this.height ?
+ dimensionsScaled.y :
+ dimensionsScaled.x
);
sx = Math.min( sx, dimensionsScaled.x - px );
diff --git a/src/mousetracker.js b/src/mousetracker.js
index 60e23747..bd0c359f 100644
--- a/src/mousetracker.js
+++ b/src/mousetracker.js
@@ -466,7 +466,7 @@
function triggerOthers( tracker, handler, event ) {
var otherHash;
for ( otherHash in ACTIVE ) {
- if ( trackers.hasOwnProperty( otherHash ) && tracker.hash != otherHash ) {
+ if ( ACTIVE.hasOwnProperty( otherHash ) && tracker.hash != otherHash ) {
handler( ACTIVE[ otherHash ], event );
}
}
@@ -479,13 +479,16 @@
*/
function onFocus( tracker, event ){
//console.log( "focus %s", event );
+ var propagate;
if ( tracker.focusHandler ) {
try {
- tracker.focusHandler(
+ propagate = tracker.focusHandler(
tracker,
event
);
- $.cancelEvent( event );
+ if( propagate === false ){
+ $.cancelEvent( event );
+ }
} catch ( e ) {
$.console.error(
"%s while executing key handler: %s",
@@ -504,13 +507,16 @@
*/
function onBlur( tracker, event ){
//console.log( "blur %s", event );
+ var propagate;
if ( tracker.blurHandler ) {
try {
- tracker.blurHandler(
+ propagate = tracker.blurHandler(
tracker,
event
);
- $.cancelEvent( event );
+ if( propagate === false ){
+ $.cancelEvent( event );
+ }
} catch ( e ) {
$.console.error(
"%s while executing key handler: %s",
@@ -537,7 +543,7 @@
event.keyCode ? event.keyCode : event.charCode,
event.shiftKey
);
- if( !propagate ){
+ if( propagate === false ){
$.cancelEvent( event );
}
} catch ( e ) {
@@ -559,7 +565,8 @@
function onMouseOver( tracker, event ) {
var event = $.getEvent( event ),
- delegate = THIS[ tracker.hash ];
+ delegate = THIS[ tracker.hash ],
+ propagate;
if ( $.Browser.vendor == $.BROWSERS.IE &&
delegate.capturing &&
@@ -585,12 +592,15 @@
if ( tracker.enterHandler ) {
try {
- tracker.enterHandler(
+ propagate = tracker.enterHandler(
tracker,
getMouseRelative( event, tracker.element ),
delegate.buttonDown,
IS_BUTTON_DOWN
);
+ if( propagate === false ){
+ $.cancelEvent( event );
+ }
} catch ( e ) {
$.console.error(
"%s while executing enter handler: %s",
@@ -609,7 +619,8 @@
*/
function onMouseOut( tracker, event ) {
var event = $.getEvent( event ),
- delegate = THIS[ tracker.hash ];
+ delegate = THIS[ tracker.hash ],
+ propagate;
if ( $.Browser.vendor == $.BROWSERS.IE &&
delegate.capturing &&
@@ -635,12 +646,16 @@
if ( tracker.exitHandler ) {
try {
- tracker.exitHandler(
+ propagate = tracker.exitHandler(
tracker,
getMouseRelative( event, tracker.element ),
delegate.buttonDown,
IS_BUTTON_DOWN
);
+
+ if( propagate === false ){
+ $.cancelEvent( event );
+ }
} catch ( e ) {
$.console.error(
"%s while executing exit handler: %s",
@@ -659,7 +674,8 @@
*/
function onMouseDown( tracker, event ) {
var event = $.getEvent( event ),
- delegate = THIS[ tracker.hash ];
+ delegate = THIS[ tracker.hash ],
+ propagate;
if ( event.button == 2 ) {
return;
@@ -673,10 +689,13 @@
if ( tracker.pressHandler ) {
try {
- tracker.pressHandler(
+ propagate = tracker.pressHandler(
tracker,
getMouseRelative( event, tracker.element )
);
+ if( propagate === false ){
+ $.cancelEvent( event );
+ }
} catch (e) {
$.console.error(
"%s while executing press handler: %s",
@@ -743,7 +762,8 @@
//were we inside the tracked element when we were pressed
insideElementPress = delegate.buttonDown,
//are we still inside the tracked element when we released
- insideElementRelease = delegate.insideElement;
+ insideElementRelease = delegate.insideElement,
+ propagate;
if ( event.button == 2 ) {
return;
@@ -753,12 +773,15 @@
if ( tracker.releaseHandler ) {
try {
- tracker.releaseHandler(
+ propagate = tracker.releaseHandler(
tracker,
getMouseRelative( event, tracker.element ),
insideElementPress,
insideElementRelease
);
+ if( propagate === false ){
+ $.cancelEvent( event );
+ }
} catch (e) {
$.console.error(
"%s while executing release handler: %s",
@@ -867,7 +890,8 @@
* @inner
*/
function onMouseWheelSpin( tracker, event ) {
- var nDelta = 0;
+ var nDelta = 0,
+ propagate;
if ( !event ) { // For IE, access the global (window) event object
event = window.event;
@@ -888,12 +912,15 @@
if ( tracker.scrollHandler ) {
try {
- tracker.scrollHandler(
+ propagate = tracker.scrollHandler(
tracker,
getMouseRelative( event, tracker.element ),
nDelta,
event.shiftKey
);
+ if( propagate === false ){
+ $.cancelEvent( event );
+ }
} catch (e) {
$.console.error(
"%s while executing scroll handler: %s",
@@ -902,8 +929,6 @@
e
);
}
-
- $.cancelEvent( event );
}
};
@@ -914,7 +939,8 @@
*/
function handleMouseClick( tracker, event ) {
var event = $.getEvent( event ),
- delegate = THIS[ tracker.hash ];
+ delegate = THIS[ tracker.hash ],
+ propagate;
if ( event.button == 2 ) {
return;
@@ -928,12 +954,15 @@
if ( tracker.clickHandler ) {
try {
- tracker.clickHandler(
+ propagate = tracker.clickHandler(
tracker,
getMouseRelative( event, tracker.element ),
quick,
event.shiftKey
);
+ if( propagate === false ){
+ $.cancelEvent( event );
+ }
} catch ( e ) {
$.console.error(
"%s while executing click handler: %s",
@@ -954,18 +983,22 @@
var event = $.getEvent( event ),
delegate = THIS[ tracker.hash ],
point = getMouseAbsolute( event ),
- delta = point.minus( delegate.lastPoint );
+ delta = point.minus( delegate.lastPoint ),
+ propagate;
delegate.lastPoint = point;
if ( tracker.dragHandler ) {
try {
- tracker.dragHandler(
+ propagate = tracker.dragHandler(
tracker,
getMouseRelative( event, tracker.element ),
delta,
event.shiftKey
);
+ if( propagate === false ){
+ $.cancelEvent( event );
+ }
} catch (e) {
$.console.error(
"%s while executing drag handler: %s",
@@ -975,7 +1008,6 @@
);
}
- $.cancelEvent( event );
}
};
diff --git a/src/navigator.js b/src/navigator.js
index 96ffdf1d..497f6894 100644
--- a/src/navigator.js
+++ b/src/navigator.js
@@ -63,13 +63,31 @@ $.Navigator = function( options ){
style.cssFloat = 'left'; //Firefox
style.styleFloat = 'left'; //IE
style.zIndex = 999999999;
+ style.cursor = 'default';
}( this.displayRegion.style ));
+ this.element.innerTracker = new $.MouseTracker({
+ element: this.element,
+ scrollHandler: function(){
+ //dont scroll the page up and down if the user is scrolling
+ //in the navigator
+ return false;
+ }
+ }).setTracking( true );
+
this.displayRegion.innerTracker = new $.MouseTracker({
element: this.displayRegion,
clickTimeThreshold: this.clickTimeThreshold,
clickDistThreshold: this.clickDistThreshold,
+ clickHandler: $.delegate( this, onCanvasClick ),
+ dragHandler: $.delegate( this, onCanvasDrag ),
+ releaseHandler: $.delegate( this, onCanvasRelease ),
+ scrollHandler: $.delegate( this, onCanvasScroll ),
focusHandler: function(){
+ var point = $.getElementPosition( _this.viewer.element );
+
+ window.scrollTo( 0, point.y );
+
_this.viewer.setControlsEnabled( true );
(function( style ){
style.border = '2px solid #437AB2';
@@ -89,12 +107,15 @@ $.Navigator = function( options ){
switch( keyCode ){
case 61://=|+
_this.viewer.viewport.zoomBy(1.1);
+ _this.viewer.viewport.applyConstraints();
return false;
case 45://-|_
_this.viewer.viewport.zoomBy(0.9);
+ _this.viewer.viewport.applyConstraints();
return false;
case 48://0|)
_this.viewer.viewport.goHome();
+ _this.viewer.viewport.applyConstraints();
return false;
case 119://w
case 87://W
@@ -102,6 +123,7 @@ $.Navigator = function( options ){
shiftKey ?
_this.viewer.viewport.zoomBy(1.1):
_this.viewer.viewport.panBy(new $.Point(0, -0.05));
+ _this.viewer.viewport.applyConstraints();
return false;
case 115://s
case 83://S
@@ -109,14 +131,17 @@ $.Navigator = function( options ){
shiftKey ?
_this.viewer.viewport.zoomBy(0.9):
_this.viewer.viewport.panBy(new $.Point(0, 0.05));
+ _this.viewer.viewport.applyConstraints();
return false;
case 97://a
case 37://left arrow
_this.viewer.viewport.panBy(new $.Point(-0.05, 0));
+ _this.viewer.viewport.applyConstraints();
return false;
case 100://d
case 39://right arrow
_this.viewer.viewport.panBy(new $.Point(0.05, 0));
+ _this.viewer.viewport.applyConstraints();
return false;
default:
//console.log( 'navigator keycode %s', keyCode );
@@ -157,23 +182,73 @@ $.extend( $.Navigator.prototype, $.EventHandler.prototype, $.Viewer.prototype, {
update: function( viewport ){
- var bounds = viewport.getBounds( true ),
- topleft = this.viewport.pixelFromPoint( bounds.getTopLeft() )
+ var bounds,
+ topleft,
+ bottomright;
+
+ if( viewport && this.viewport ){
+ bounds = viewport.getBounds( true );
+ topleft = this.viewport.pixelFromPoint( bounds.getTopLeft() );
bottomright = this.viewport.pixelFromPoint( bounds.getBottomRight() );
- //update style for navigator-box
- (function(style){
+ //update style for navigator-box
+ (function(style){
- style.top = topleft.y + 'px';
- style.left = topleft.x + 'px';
- style.width = ( Math.abs( topleft.x - bottomright.x ) - 3 ) + 'px';
- style.height = ( Math.abs( topleft.y - bottomright.y ) - 3 ) + 'px';
+ style.top = topleft.y + 'px';
+ style.left = topleft.x + 'px';
+ style.width = ( Math.abs( topleft.x - bottomright.x ) - 3 ) + 'px';
+ style.height = ( Math.abs( topleft.y - bottomright.y ) - 3 ) + 'px';
- }( this.displayRegion.style ));
+ }( this.displayRegion.style ));
+ }
}
});
+function onCanvasClick( tracker, position, quick, shift ) {
+ this.displayRegion.focus();
+};
+
+
+function onCanvasDrag( tracker, position, delta, shift ) {
+ if ( this.viewer.viewport ) {
+ if( !this.panHorizontal ){
+ delta.x = 0;
+ }
+ if( !this.panVertical ){
+ delta.y = 0;
+ }
+ this.viewer.viewport.panBy(
+ this.viewport.deltaPointsFromPixels(
+ delta
+ )
+ );
+ }
+};
+
+
+function onCanvasRelease( tracker, position, insideElementPress, insideElementRelease ) {
+ if ( insideElementPress && this.viewer.viewport ) {
+ this.viewer.viewport.applyConstraints();
+ }
+};
+
+function onCanvasScroll( tracker, position, scroll, shift ) {
+ var factor;
+ if ( this.viewer.viewport ) {
+ factor = Math.pow( this.zoomPerScroll, scroll );
+ this.viewer.viewport.zoomBy(
+ factor,
+ //this.viewport.pointFromPixel( position, true )
+ this.viewport.getCenter()
+ );
+ this.viewer.viewport.applyConstraints();
+ }
+ //cancels event
+ return false;
+};
+
+
}( OpenSeadragon ));
\ No newline at end of file
diff --git a/src/openseadragon.js b/src/openseadragon.js
index b0ebb4d9..3887dea6 100644
--- a/src/openseadragon.js
+++ b/src/openseadragon.js
@@ -188,6 +188,12 @@
* interactions include draging the image in a plane, and zooming in toward
* and away from the image.
*
+ * @param {Boolean} [options.preserveViewport=false]
+ * If the viewer has been configured with a sequence of tile sources, then
+ * normally navigating to through each image resets the viewport to 'home'
+ * position. If preserveViewport is set to true, then the viewport position
+ * is preserved when navigating between images in the sequence.
+ *
* @param {String} [options.prefixUrl='']
* Appends the prefixUrl to navImages paths, which is very useful
* since the default paths are rarely useful for production
@@ -428,43 +434,50 @@ OpenSeadragon = window.OpenSeadragon || function( options ){
* @static
*/
DEFAULT_SETTINGS: {
- xmlPath: null,
- tileSources: null,
- debugMode: true,
- animationTime: 1.5,
- blendTime: 0.5,
- alwaysBlend: false,
- autoHideControls: true,
- immediateRender: false,
- wrapHorizontal: false,
- wrapVertical: false,
- minZoomImageRatio: 0.8,
- maxZoomPixelRatio: 2,
- visibilityRatio: 0.5,
- springStiffness: 5.0,
- imageLoaderLimit: 0,
- clickTimeThreshold: 200,
- clickDistThreshold: 5,
- zoomPerClick: 2.0,
- zoomPerScroll: 1.2,
- zoomPerSecond: 2.0,
- showNavigationControl: true,
-
- showNavigator: false,
- navigatorElement: null,
- navigatorHeight: null,
- navigatorWidth: null,
- navigatorPosition: null,
- navigatorSizeRatio: 0.25,
+ //DATA SOURCE DETAILS
+ xmlPath: null,
+ tileSources: null,
+ tileHost: null,
+
+ //INTERFACE FEATURES
+ debugMode: true,
+ animationTime: 1.5,
+ blendTime: 0.5,
+ alwaysBlend: false,
+ autoHideControls: true,
+ immediateRender: false,
+ wrapHorizontal: false,
+ wrapVertical: false,
+ panHorizontal: true,
+ panVertical: true,
+ visibilityRatio: 0.5,
+ springStiffness: 5.0,
+ clickTimeThreshold: 200,
+ clickDistThreshold: 5,
+ zoomPerClick: 2.0,
+ zoomPerScroll: 1.2,
+ zoomPerSecond: 2.0,
+ showNavigationControl: true,
+ controlsFadeDelay: 2000,
+ controlsFadeLength: 1500,
+ mouseNavEnabled: true,
+ showNavigator: false,
+ navigatorElement: null,
+ navigatorHeight: null,
+ navigatorWidth: null,
+ navigatorPosition: null,
+ navigatorSizeRatio: 0.25,
+ preserveViewport: false,
- //These two were referenced but never defined
- controlsFadeDelay: 2000,
- controlsFadeLength: 1500,
+ //PERFORMANCE SETTINGS
+ minPixelRatio: 0.5,
+ imageLoaderLimit: 0,
+ maxImageCacheCount: 200,
+ minZoomImageRatio: 0.9,
+ maxZoomPixelRatio: 2,
- maxImageCacheCount: 200,
- minPixelRatio: 0.5,
- mouseNavEnabled: true,
- prefixUrl: null,
+ //INTERFACE RESOURCE SETTINGS
+ prefixUrl: null,
navImages: {
zoomIn: {
REST: '/images/zoomin_rest.png',
@@ -489,6 +502,18 @@ OpenSeadragon = window.OpenSeadragon || function( options ){
GROUP: '/images/fullpage_grouphover.png',
HOVER: '/images/fullpage_hover.png',
DOWN: '/images/fullpage_pressed.png'
+ },
+ previous: {
+ REST: '/images/previous_rest.png',
+ GROUP: '/images/previous_grouphover.png',
+ HOVER: '/images/previous_hover.png',
+ DOWN: '/images/previous_pressed.png'
+ },
+ next: {
+ REST: '/images/next_rest.png',
+ GROUP: '/images/next_grouphover.png',
+ HOVER: '/images/next_hover.png',
+ DOWN: '/images/next_pressed.png'
}
}
},
@@ -1192,7 +1217,7 @@ OpenSeadragon = window.OpenSeadragon || function( options ){
* @param {String} xmlString
* @param {Function} callback
*/
- createFromDZI: function( dzi, callback ) {
+ createFromDZI: function( dzi, callback, tileHost ) {
var async = typeof ( callback ) == "function",
xmlUrl = dzi.substring(0,1) != '<' ? dzi : null,
xmlString = xmlUrl ? null : dzi,
@@ -1203,7 +1228,12 @@ OpenSeadragon = window.OpenSeadragon || function( options ){
tilesUrl;
- if( xmlUrl ){
+ if( tileHost ){
+
+ tilesUrl = tileHost + "/_files/";
+
+ } else if( xmlUrl ) {
+
urlParts = xmlUrl.split( '/' );
filename = urlParts[ urlParts.length - 1 ];
lastDot = filename.lastIndexOf( '.' );
@@ -1213,6 +1243,7 @@ OpenSeadragon = window.OpenSeadragon || function( options ){
}
tilesUrl = urlParts.join( '/' ) + "_files/";
+
}
function finish( func, obj ) {
diff --git a/src/strings.js b/src/strings.js
index c0301641..22e676e6 100644
--- a/src/strings.js
+++ b/src/strings.js
@@ -6,28 +6,30 @@
// pythons gettext might be a reasonable approach.
var I18N = {
Errors: {
- Failure: "Sorry, but Seadragon Ajax can't run on your browser!\n" +
- "Please try using IE 7 or Firefox 3.\n",
- Dzc: "Sorry, we don't support Deep Zoom Collections!",
- Dzi: "Hmm, this doesn't appear to be a valid Deep Zoom Image.",
- Xml: "Hmm, this doesn't appear to be a valid Deep Zoom Image.",
- Empty: "You asked us to open nothing, so we did just that.",
- ImageFormat: "Sorry, we don't support {0}-based Deep Zoom Images.",
- Security: "It looks like a security restriction stopped us from " +
- "loading this Deep Zoom Image.",
- Status: "This space unintentionally left blank ({0} {1}).",
- Unknown: "Whoops, something inexplicably went wrong. Sorry!"
+ Failure: "Sorry, but Seadragon Ajax can't run on your browser!\n" +
+ "Please try using IE 7 or Firefox 3.\n",
+ Dzc: "Sorry, we don't support Deep Zoom Collections!",
+ Dzi: "Hmm, this doesn't appear to be a valid Deep Zoom Image.",
+ Xml: "Hmm, this doesn't appear to be a valid Deep Zoom Image.",
+ Empty: "You asked us to open nothing, so we did just that.",
+ ImageFormat: "Sorry, we don't support {0}-based Deep Zoom Images.",
+ Security: "It looks like a security restriction stopped us from " +
+ "loading this Deep Zoom Image.",
+ Status: "This space unintentionally left blank ({0} {1}).",
+ Unknown: "Whoops, something inexplicably went wrong. Sorry!"
},
Messages: {
- Loading: "Loading..."
+ Loading: "Loading..."
},
Tooltips: {
- FullPage: "Toggle full page",
- Home: "Go home",
- ZoomIn: "Zoom in",
- ZoomOut: "Zoom out"
+ FullPage: "Toggle full page",
+ Home: "Go home",
+ ZoomIn: "Zoom in",
+ ZoomOut: "Zoom out",
+ NextPage: "Next page",
+ PreviousPage: "Previous page"
}
};
@@ -41,12 +43,13 @@ $.extend( $, {
getString: function( prop ) {
var props = prop.split('.'),
- string = I18N,
+ string = null,
args = arguments,
i;
for ( i = 0; i < props.length; i++ ) {
- string = string[ props[ i ] ] || {}; // in case not a subproperty
+ // in case not a subproperty
+ string = I18N[ props[ i ] ] || {};
}
if ( typeof( string ) != "string" ) {
diff --git a/src/tile.js b/src/tile.js
index d677153e..09632e8c 100644
--- a/src/tile.js
+++ b/src/tile.js
@@ -45,7 +45,7 @@ $.Tile = function(level, x, y, bounds, exists, url) {
this.loading = false;
this.element = null;
- this.image = null;
+ this.image = null;
this.style = null;
this.position = null;
@@ -81,7 +81,7 @@ $.Tile.prototype = {
var position = this.position.apply( Math.floor ),
size = this.size.apply( Math.ceil );
- if ( !this.loaded ) {
+ if ( !this.loaded || !this.image ) {
$.console.warn(
"Attempting to draw tile %s when it's not yet loaded.",
this.toString()
@@ -90,9 +90,9 @@ $.Tile.prototype = {
}
if ( !this.element ) {
- this.element = $.makeNeutralElement("img");
- this.element.src = this.url;
- this.style = this.element.style;
+ this.element = $.makeNeutralElement("img");
+ this.element.src = this.url;
+ this.style = this.element.style;
this.style.position = "absolute";
this.style.msInterpolationMode = "nearest-neighbor";
@@ -122,14 +122,13 @@ $.Tile.prototype = {
var position = this.position,
size = this.size;
- if ( !this.loaded ) {
+ if ( !this.loaded || !this.image ) {
$.console.warn(
"Attempting to draw tile %s when it's not yet loaded.",
this.toString()
);
return;
}
-
context.globalAlpha = this.opacity;
context.drawImage( this.image, position.x, position.y, size.x, size.y );
},
diff --git a/src/viewer.js b/src/viewer.js
index 0e2da784..12e977a4 100644
--- a/src/viewer.js
+++ b/src/viewer.js
@@ -59,16 +59,25 @@ $.Viewer = function( options ) {
delete options.config;
}
+ //Public properties
//Allow the options object to override global defaults
$.extend( true, this, {
- id: options.id,
- hash: options.id,
- overlays: [],
- overlayControls: [],
+ //internal state and dom identifiers
+ id: options.id,
+ hash: options.id,
+
+ //dom nodes
+ element: null,
+ canvas: null,
+ container: null,
+
+ //TODO: not sure how to best describe these
+ overlays: [],
+ overlayControls:[],
//private state properties
- previousBody: [],
+ previousBody: [],
//This was originally initialized in the constructor and so could never
//have anything in it. now it can because we allow it to be specified
@@ -84,13 +93,76 @@ $.Viewer = function( options ) {
drawer: null,
viewport: null,
navigator: null,
+
+ //UI image resources
+ //TODO: rename navImages to uiImages
+ navImages: null,
+
+ //interface button controls
+ buttons: null,
+
+ //TODO: this is defunct so safely remove it
profiler: null
}, $.DEFAULT_SETTINGS, options );
+ //Private state properties
+ THIS[ this.hash ] = {
+ "fsBoundsDelta": new $.Point( 1, 1 ),
+ "prevContainerSize": null,
+ "lastOpenStartTime": 0,
+ "lastOpenEndTime": 0,
+ "animating": false,
+ "forceRedraw": false,
+ "mouseInside": false,
+ "group": null,
+ // whether we should be continuously zooming
+ "zooming": false,
+ // how much we should be continuously zooming by
+ "zoomFactor": null,
+ "lastZoomTime": null,
+ // did we decide this viewer has a sequence of tile sources
+ "sequenced": false,
+ "sequence": 0
+ };
+
+ //Inherit some behaviors and properties
$.EventHandler.call( this );
$.ControlDock.call( this, options );
+ //Deal with tile sources
+ var initialTileSource,
+ customTileSource;
+
+ if ( this.xmlPath ){
+ //Deprecated option. Now it is preferred to use the tileSources option
+ this.tileSources = [ this.xmlPath ];
+ }
+
+ if ( this.tileSources ){
+ //tileSources is a complex option...
+ //It can be a string, object, function, or an array of any of these.
+ // - A String implies a DZI
+ // - An Srray of Objects implies a simple image
+ // - A Function implies a custom tile source callback
+ // - An Array that is not an Array of simple Objects implies a sequence
+ // of tile sources which can be any of the above
+ if( $.isArray( this.tileSources ) ){
+ if( $.isPlainObject( this.tileSources[ 0 ] ) ){
+ //This is a non-sequenced legacy tile source
+ initialTileSource = this.tileSources;
+ } else {
+ //Sequenced tile source
+ initialTileSource = this.tileSources[ 0 ];
+ THIS[ this.hash ].sequenced = true;
+ }
+ } else {
+ initialTileSource = this.tileSources;
+ }
+
+ this.openTileSource( initialTileSource );
+ }
+
this.element = this.element || document.getElementById( this.id );
this.canvas = $.makeNeutralElement( "div" );
@@ -112,7 +184,7 @@ $.Viewer = function( options ) {
container.textAlign = "left"; // needed to protect against
}( this.container.style ));
- this.container.insertBefore( this.canvas, this.container.firstChild);
+ this.container.insertBefore( this.canvas, this.container.firstChild );
this.element.appendChild( this.container );
//Used for toggling between fullscreen and default container size
@@ -123,16 +195,6 @@ $.Viewer = function( options ) {
this.bodyOverflow = document.body.style.overflow;
this.docOverflow = document.documentElement.style.overflow;
- THIS[ this.hash ] = {
- "fsBoundsDelta": new $.Point( 1, 1 ),
- "prevContainerSize": null,
- "lastOpenStartTime": 0,
- "lastOpenEndTime": 0,
- "animating": false,
- "forceRedraw": false,
- "mouseInside": false
- };
-
this.innerTracker = new $.MouseTracker({
element: this.canvas,
clickTimeThreshold: this.clickTimeThreshold,
@@ -153,16 +215,6 @@ $.Viewer = function( options ) {
}).setTracking( this.mouseNavEnabled ? true : false ); // always tracking
- //private state properties
- $.extend( THIS[ this.hash ], {
- "group": null,
- // whether we should be continuously zooming
- "zooming": false,
- // how much we should be continuously zooming by
- "zoomFactor": null,
- "lastZoomTime": null
- });
-
//////////////////////////////////////////////////////////////////////////
// Navigation Controls
//////////////////////////////////////////////////////////////////////////
@@ -175,16 +227,15 @@ $.Viewer = function( options ) {
onFullPageHandler = $.delegate( this, onFullPage ),
onFocusHandler = $.delegate( this, onFocus ),
onBlurHandler = $.delegate( this, onBlur ),
- navImages = this.navImages;
+ onNextHandler = $.delegate( this, onNext ),
+ onPreviousHandler = $.delegate( this, onPrevious ),
+ navImages = this.navImages,
+ buttons = [];
- this.zoomInButton = null;
- this.zoomOutButton = null;
- this.goHomeButton = null;
- this.fullPageButton = null;
if( this.showNavigationControl ){
- this.zoomInButton = new $.Button({
+ buttons.push( this.zoomInButton = new $.Button({
clickTimeThreshold: this.clickTimeThreshold,
clickDistThreshold: this.clickDistThreshold,
tooltip: $.getString( "Tooltips.ZoomIn" ),
@@ -199,9 +250,9 @@ $.Viewer = function( options ) {
onExit: endZoomingHandler,
onFocus: onFocusHandler,
onBlur: onBlurHandler
- });
+ }));
- this.zoomOutButton = new $.Button({
+ buttons.push( this.zoomOutButton = new $.Button({
clickTimeThreshold: this.clickTimeThreshold,
clickDistThreshold: this.clickDistThreshold,
tooltip: $.getString( "Tooltips.ZoomOut" ),
@@ -216,9 +267,9 @@ $.Viewer = function( options ) {
onExit: endZoomingHandler,
onFocus: onFocusHandler,
onBlur: onBlurHandler
- });
+ }));
- this.goHomeButton = new $.Button({
+ buttons.push( this.homeButton = new $.Button({
clickTimeThreshold: this.clickTimeThreshold,
clickDistThreshold: this.clickDistThreshold,
tooltip: $.getString( "Tooltips.Home" ),
@@ -229,9 +280,9 @@ $.Viewer = function( options ) {
onRelease: onHomeHandler,
onFocus: onFocusHandler,
onBlur: onBlurHandler
- });
+ }));
- this.fullPageButton = new $.Button({
+ buttons.push( this.fullPageButton = new $.Button({
clickTimeThreshold: this.clickTimeThreshold,
clickDistThreshold: this.clickDistThreshold,
tooltip: $.getString( "Tooltips.FullPage" ),
@@ -242,17 +293,44 @@ $.Viewer = function( options ) {
onRelease: onFullPageHandler,
onFocus: onFocusHandler,
onBlur: onBlurHandler
- });
+ }));
- this.buttons = new $.ButtonGroup({
+ if( THIS[ this.hash ].sequenced ){
+
+ buttons.push( this.previousButton = new $.Button({
+ clickTimeThreshold: this.clickTimeThreshold,
+ clickDistThreshold: this.clickDistThreshold,
+ tooltip: $.getString( "Tooltips.PreviousPage" ),
+ srcRest: resolveUrl( this.prefixUrl, navImages.previous.REST ),
+ srcGroup: resolveUrl( this.prefixUrl, navImages.previous.GROUP ),
+ srcHover: resolveUrl( this.prefixUrl, navImages.previous.HOVER ),
+ srcDown: resolveUrl( this.prefixUrl, navImages.previous.DOWN ),
+ onRelease: onPreviousHandler,
+ onFocus: onFocusHandler,
+ onBlur: onBlurHandler
+ }));
+
+ buttons.push( this.nextButton = new $.Button({
+ clickTimeThreshold: this.clickTimeThreshold,
+ clickDistThreshold: this.clickDistThreshold,
+ tooltip: $.getString( "Tooltips.NextPage" ),
+ srcRest: resolveUrl( this.prefixUrl, navImages.next.REST ),
+ srcGroup: resolveUrl( this.prefixUrl, navImages.next.GROUP ),
+ srcHover: resolveUrl( this.prefixUrl, navImages.next.HOVER ),
+ srcDown: resolveUrl( this.prefixUrl, navImages.next.DOWN ),
+ onRelease: onNextHandler,
+ onFocus: onFocusHandler,
+ onBlur: onBlurHandler
+ }));
+
+ this.previousButton.disable();
+
+ }
+
+ this.buttons = new $.ButtonGroup({
+ buttons: buttons,
clickTimeThreshold: this.clickTimeThreshold,
- clickDistThreshold: this.clickDistThreshold,
- buttons: [
- this.zoomInButton,
- this.zoomOutButton,
- this.goHomeButton,
- this.fullPageButton
- ]
+ clickDistThreshold: this.clickDistThreshold
});
this.navControl = this.buttons.element;
@@ -274,73 +352,10 @@ $.Viewer = function( options ) {
);
}
- //Instantiate a navigator if configured
- if ( this.showNavigator ){
- this.navigator = new $.Navigator({
- id: this.navigatorElement,
- position: this.navigatorPosition,
- sizeRatio: this.navigatorSizeRatio,
- height: this.navigatorHeight,
- width: this.navigatorWidth,
- tileSources: this.tileSources,
- prefixUrl: this.prefixUrl,
- overlays: this.overlays,
- viewer: this
- });
- }
-
window.setTimeout( function(){
beginControlsAutoHide( _this );
}, 1 ); // initial fade out
- var initialTileSource,
- customTileSource;
-
- if ( this.xmlPath ){
- //Deprecated option. Now it is preferred to use the tileSources option
- this.tileSources = [ this.xmlPath ];
- }
-
- if ( this.tileSources ){
- //tileSource is a complex option...
- //It can be a string, object, function, or an array of any of these.
- //A string implies a DZI
- //An object implies a simple image
- //A function implies a custom tile source callback
- //An array implies a sequence of tile sources which can be any of the
- //above
- if( $.isArray( this.tileSources ) ){
- if( $.isPlainObject( this.tileSources[ 0 ] ) ){
- //This is a non-sequenced legacy tile source
- initialTileSource = this.tileSources;
- } else {
- //Sequenced tile source
- initialTileSource = this.tileSources[ 0 ];
- }
- } else {
- initialTileSource = this.tileSources
- }
-
- if ( $.type( initialTileSource ) == 'string') {
- //Standard DZI format
- this.openDzi( initialTileSource );
- } else if ( $.isArray( initialTileSource ) ){
- //Legacy image pyramid
- this.open( new $.LegacyTileSource( initialTileSource ) );
- } else if ( $.isPlainObject( initialTileSource ) && $.isFunction( initialTileSource.getTileUrl ) ){
- //Custom tile source
- customTileSource = new $.TileSource(
- initialTileSource.width,
- initialTileSource.height,
- initialTileSource.tileSize,
- initialTileSource.tileOverlap,
- initialTileSource.minLevel,
- initialTileSource.maxLevel
- );
- customTileSource.getTileUrl = initialTileSource.getTileUrl;
- this.open( customTileSource );
- }
- }
};
$.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype, {
@@ -370,7 +385,8 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
dzi,
function( source ){
_this.open( source );
- }
+ },
+ this.tileHost
);
return this;
},
@@ -381,10 +397,34 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
* @return {OpenSeadragon.Viewer} Chainable.
*/
openTileSource: function ( tileSource ) {
- var _this = this;
- window.setTimeout( function () {
- _this.open( tileSource );
- }, 1 );
+ var _this = this,
+ customTileSource;
+
+ setTimeout(function(){
+ if ( $.type( tileSource ) == 'string') {
+ //Standard DZI format
+ _this.openDzi( tileSource );
+ } else if ( $.isArray( tileSource ) ){
+ //Legacy image pyramid
+ _this.open( new $.LegacyTileSource( tileSource ) );
+ } else if ( $.isPlainObject( tileSource ) && $.isFunction( tileSource.getTileUrl ) ){
+ //Custom tile source
+ customTileSource = new $.TileSource(
+ tileSource.width,
+ tileSource.height,
+ tileSource.tileSize,
+ tileSource.tileOverlap,
+ tileSource.minLevel,
+ tileSource.maxLevel
+ );
+ customTileSource.getTileUrl = tileSource.getTileUrl;
+ _this.open( customTileSource );
+ } else {
+ //can assume it's already a tile source implementation
+ _this.open( tileSource );
+ }
+ }, 1);
+
return this;
},
@@ -399,7 +439,7 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
i;
if ( this.source ) {
- this.close();
+ this.close( );
}
// to ignore earlier opens
@@ -419,7 +459,7 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
this.source = source;
}
- this.viewport = new $.Viewport({
+ this.viewport = this.viewport ? this.viewport : new $.Viewport({
containerSize: THIS[ this.hash ].prevContainerSize,
contentSize: this.source.dimensions,
springStiffness: this.springStiffness,
@@ -430,6 +470,9 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
wrapHorizontal: this.wrapHorizontal,
wrapVertical: this.wrapVertical
});
+ if( this.preserveVewport ){
+ this.viewport.resetContentSize( this.source.dimensions );
+ }
this.drawer = new $.Drawer({
source: this.source,
@@ -447,6 +490,22 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
minPixelRatio: this.minPixelRatio
});
+ //Instantiate a navigator if configured
+ if ( this.showNavigator && ! this.navigator ){
+ this.navigator = new $.Navigator({
+ id: this.navigatorElement,
+ position: this.navigatorPosition,
+ sizeRatio: this.navigatorSizeRatio,
+ height: this.navigatorHeight,
+ width: this.navigatorWidth,
+ tileSources: this.tileSources,
+ tileHost: this.tileHost,
+ prefixUrl: this.prefixUrl,
+ overlays: this.overlays,
+ viewer: this
+ });
+ }
+
//this.profiler = new $.Profiler();
THIS[ this.hash ].animating = false;
@@ -487,6 +546,11 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
}
VIEWERS[ this.hash ] = this;
this.raiseEvent( "open" );
+
+ if( this.navigator ){
+ this.navigator.open( source );
+ }
+
return this;
},
@@ -495,10 +559,10 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
* @name OpenSeadragon.Viewer.prototype.close
* @return {OpenSeadragon.Viewer} Chainable.
*/
- close: function () {
+ close: function ( ) {
this.source = null;
- this.viewport = null;
this.drawer = null;
+ this.viewport = this.preserveViewport ? this.viewport : null;
//this.profiler = null;
this.canvas.innerHTML = "";
@@ -722,6 +786,9 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
viewer = VIEWERS[ hash ];
if( viewer !== this && viewer != this.navigator ){
viewer.open( viewer.source );
+ if( viewer.navigator ){
+ viewer.navigator.open( viewer.source );
+ }
}
}
}
@@ -757,10 +824,11 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
});
+
+
///////////////////////////////////////////////////////////////////////////////
// Schedulers provide the general engine for animation
///////////////////////////////////////////////////////////////////////////////
-
function scheduleUpdate( viewer, updateFunc, prevUpdateTime ){
var currentTime,
targetTime,
@@ -783,6 +851,7 @@ function scheduleUpdate( viewer, updateFunc, prevUpdateTime ){
}, deltaTime );
};
+
//provides a sequence in the fade animation
function scheduleControlsFade( viewer ) {
window.setTimeout( function(){
@@ -790,6 +859,7 @@ function scheduleControlsFade( viewer ) {
}, 20);
};
+
//initiates an animation to hide the controls
function beginControlsAutoHide( viewer ) {
if ( !viewer.autoHideControls ) {
@@ -831,6 +901,7 @@ function updateControlsFade( viewer ) {
}
};
+
//stop the fade animation on the controls and show them
function abortControlsAutoHide( viewer ) {
var i;
@@ -841,6 +912,7 @@ function abortControlsAutoHide( viewer ) {
};
+
///////////////////////////////////////////////////////////////////////////////
// Default view event handlers.
///////////////////////////////////////////////////////////////////////////////
@@ -869,6 +941,12 @@ function onCanvasClick( tracker, position, quick, shift ) {
function onCanvasDrag( tracker, position, delta, shift ) {
if ( this.viewport ) {
+ if( !this.panHorizontal ){
+ delta.x = 0;
+ }
+ if( !this.panVertical ){
+ delta.y = 0;
+ }
this.viewport.panBy(
this.viewport.deltaPointsFromPixels(
delta.negate()
@@ -893,6 +971,8 @@ function onCanvasScroll( tracker, position, scroll, shift ) {
);
this.viewport.applyConstraints();
}
+ //cancels event
+ return false;
};
function onContainerExit( tracker, position, buttonDownElement, buttonDownAny ) {
@@ -988,36 +1068,42 @@ function updateOnce( viewer ) {
//viewer.profiler.endUpdate();
};
+
+
///////////////////////////////////////////////////////////////////////////////
// Navigation Controls
///////////////////////////////////////////////////////////////////////////////
-
function resolveUrl( prefix, url ) {
return prefix ? prefix + url : url;
};
+
function beginZoomingIn() {
THIS[ this.hash ].lastZoomTime = +new Date();
THIS[ this.hash ].zoomFactor = this.zoomPerSecond;
THIS[ this.hash ].zooming = true;
scheduleZoom( this );
-}
+};
+
function beginZoomingOut() {
THIS[ this.hash ].lastZoomTime = +new Date();
THIS[ this.hash ].zoomFactor = 1.0 / this.zoomPerSecond;
THIS[ this.hash ].zooming = true;
scheduleZoom( this );
-}
+};
+
function endZooming() {
THIS[ this.hash ].zooming = false;
-}
+};
+
function scheduleZoom( viewer ) {
window.setTimeout( $.delegate( viewer, doZoom ), 10 );
-}
+};
+
function doZoom() {
var currentTime,
@@ -1036,6 +1122,7 @@ function doZoom() {
}
};
+
function doSingleZoomIn() {
if ( this.viewport ) {
THIS[ this.hash ].zooming = false;
@@ -1046,6 +1133,7 @@ function doSingleZoomIn() {
}
};
+
function doSingleZoomOut() {
if ( this.viewport ) {
THIS[ this.hash ].zooming = false;
@@ -1056,17 +1144,20 @@ function doSingleZoomOut() {
}
};
+
function lightUp() {
this.buttons.emulateEnter();
this.buttons.emulateExit();
};
+
function onHome() {
if ( this.viewport ) {
this.viewport.goHome();
}
};
+
function onFullPage() {
this.setFullPage( !this.isFullPage() );
// correct for no mouseout event on change
@@ -1077,4 +1168,47 @@ function onFullPage() {
}
};
+
+function onPrevious(){
+ var previous = THIS[ this.hash ].sequence - 1,
+ preserveVewport = true;
+ if( previous >= 0 ){
+
+ THIS[ this.hash ].sequence = previous;
+
+ if( 0 === previous ){
+ //Disable previous button
+ this.previousButton.disable();
+ }
+ if( this.tileSources.length > 0 ){
+ //Enable next button
+ this.nextButton.enable();
+ }
+
+ this.openTileSource( this.tileSources[ previous ] );
+ }
+};
+
+
+function onNext(){
+ var next = THIS[ this.hash ].sequence + 1,
+ preserveVewport = true;
+ if( this.tileSources.length > next ){
+
+ THIS[ this.hash ].sequence = next;
+
+ if( ( this.tileSources.length - 1 ) === next ){
+ //Disable next button
+ this.nextButton.disable();
+ }
+ if( next > 0 ){
+ //Enable previous button
+ this.previousButton.enable();
+ }
+
+ this.openTileSource( this.tileSources[ next ] );
+ }
+};
+
+
}( OpenSeadragon ));
diff --git a/src/viewport.js b/src/viewport.js
index b6807edd..4079a75c 100644
--- a/src/viewport.js
+++ b/src/viewport.js
@@ -46,9 +46,6 @@ $.Viewport = function( options ) {
}, options );
-
- this.contentAspect = this.contentSize.x / this.contentSize.y;
- this.contentHeight = this.contentSize.y / this.contentSize.x;
this.centerSpringX = new $.Spring({
initial: 0,
springStiffness: this.springStiffness,
@@ -64,19 +61,39 @@ $.Viewport = function( options ) {
springStiffness: this.springStiffness,
animationTime: this.animationTime
});
- this.homeBounds = new $.Rect( 0, 0, 1, this.contentHeight );
+ this.resetContentSize( this.contentSize );
this.goHome( true );
+ //this.fitHorizontally( true );
this.update();
};
$.Viewport.prototype = {
+ resetContentSize: function( contentSize ){
+ this.contentSize = contentSize;
+ this.contentAspectX = this.contentSize.x / this.contentSize.y;
+ this.contentAspectY = this.contentSize.y / this.contentSize.x;
+ this.homeBounds = new $.Rect(
+ 0,
+ 0,
+ 1,
+ this.contentAspectY
+ );
+ this.fitWidthBounds = new $.Rect( 0, 0, 1, this.contentAspectX );
+ this.fitHeightBounds = new $.Rect( 0, 0, 1, this.contentAspectY );
+ },
+
/**
* @function
*/
getHomeZoom: function() {
- var aspectFactor = this.contentAspect / this.getAspectRatio();
+
+ var aspectFactor = Math.min(
+ this.contentAspectX,
+ this.contentAspectY
+ ) / this.getAspectRatio();
+
return ( aspectFactor >= 1 ) ?
1 :
aspectFactor;
@@ -229,7 +246,7 @@ $.Viewport.prototype = {
left = bounds.x + bounds.width;
right = 1 - bounds.x;
top = bounds.y + bounds.height;
- bottom = this.contentHeight - bounds.y;
+ bottom = this.contentAspectY - bounds.y;
if ( this.wrapHorizontal ) {
//do nothing
@@ -311,15 +328,22 @@ $.Viewport.prototype = {
this.containerSize.x / newBounds.width
);
-
this.zoomTo( newZoom, referencePoint, immediately );
},
+
+ /**
+ * @function
+ * @param {Boolean} immediately
+ */
+ goHome: function( immediately ) {
+ return this.fitVertically( immediately );
+ },
/**
* @function
* @param {Boolean} immediately
*/
- goHome: function( immediately ) {
+ fitVertically: function( immediately ) {
var center = this.getCenter();
if ( this.wrapHorizontal ) {
@@ -330,8 +354,8 @@ $.Viewport.prototype = {
if ( this.wrapVertical ) {
center.y = (
- this.contentHeight + ( center.y % this.contentHeight )
- ) % this.contentHeight;
+ this.contentAspectY + ( center.y % this.contentAspectY )
+ ) % this.contentAspectY;
this.centerSpringY.resetTo( center.y );
this.centerSpringY.update();
}
@@ -339,6 +363,31 @@ $.Viewport.prototype = {
this.fitBounds( this.homeBounds, immediately );
},
+ /**
+ * @function
+ * @param {Boolean} immediately
+ */
+ fitHorizontally: function( immediately ) {
+ var center = this.getCenter();
+
+ if ( this.wrapHorizontal ) {
+ center.x = (
+ this.contentAspectX + ( center.x % this.contentAspectX )
+ ) % this.contentAspectX;
+ this.centerSpringX.resetTo( center.x );
+ this.centerSpringX.update();
+ }
+
+ if ( this.wrapVertical ) {
+ center.y = ( 1 + ( center.y % 1 ) ) % 1;
+ this.centerSpringY.resetTo( center.y );
+ this.centerSpringY.update();
+ }
+
+ this.fitBounds( this.fitWidthBounds, immediately );
+ },
+
+
/**
* @function
* @param {OpenSeadragon.Point} delta