mirror of
https://github.com/openseadragon/openseadragon.git
synced 2024-11-26 15:16:10 +03:00
f1865c3878
When doing pinch to zoom, we need to do pan first and then zoom, so that the center point is correct. Otherwise, the pan will computed on the unzoomed coordinates, giving an impression of zooming toward the center
3745 lines
146 KiB
JavaScript
3745 lines
146 KiB
JavaScript
/*
|
|
* OpenSeadragon - Viewer
|
|
*
|
|
* Copyright (C) 2009 CodePlex Foundation
|
|
* Copyright (C) 2010-2013 OpenSeadragon contributors
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
*
|
|
* - Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* - Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* - Neither the name of CodePlex Foundation nor the names of its
|
|
* contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
(function( $ ){
|
|
|
|
// dictionary from hash to private properties
|
|
var THIS = {};
|
|
var nextHash = 1;
|
|
|
|
/**
|
|
*
|
|
* The main point of entry into creating a zoomable image on the page.<br>
|
|
* <br>
|
|
* We have provided an idiomatic javascript constructor which takes
|
|
* a single object, but still support the legacy positional arguments.<br>
|
|
* <br>
|
|
* The options below are given in order that they appeared in the constructor
|
|
* as arguments and we translate a positional call into an idiomatic call.<br>
|
|
* <br>
|
|
* To create a viewer, you can use either of this methods:<br>
|
|
* <ul>
|
|
* <li><code>var viewer = new OpenSeadragon.Viewer(options);</code></li>
|
|
* <li><code>var viewer = OpenSeadragon(options);</code></li>
|
|
* </ul>
|
|
* @class Viewer
|
|
* @classdesc The main OpenSeadragon viewer class.
|
|
*
|
|
* @memberof OpenSeadragon
|
|
* @extends OpenSeadragon.EventSource
|
|
* @extends OpenSeadragon.ControlDock
|
|
* @param {OpenSeadragon.Options} options - Viewer options.
|
|
*
|
|
**/
|
|
$.Viewer = function( options ) {
|
|
|
|
var args = arguments,
|
|
_this = this,
|
|
i;
|
|
|
|
|
|
//backward compatibility for positional args while preferring more
|
|
//idiomatic javascript options object as the only argument
|
|
if( !$.isPlainObject( options ) ){
|
|
options = {
|
|
id: args[ 0 ],
|
|
xmlPath: args.length > 1 ? args[ 1 ] : undefined,
|
|
prefixUrl: args.length > 2 ? args[ 2 ] : undefined,
|
|
controls: args.length > 3 ? args[ 3 ] : undefined,
|
|
overlays: args.length > 4 ? args[ 4 ] : undefined
|
|
};
|
|
}
|
|
|
|
//options.config and the general config argument are deprecated
|
|
//in favor of the more direct specification of optional settings
|
|
//being pass directly on the options object
|
|
if ( options.config ){
|
|
$.extend( true, options, options.config );
|
|
delete options.config;
|
|
}
|
|
|
|
//Public properties
|
|
//Allow the options object to override global defaults
|
|
$.extend( true, this, {
|
|
|
|
//internal state and dom identifiers
|
|
id: options.id,
|
|
hash: options.hash || nextHash++,
|
|
/**
|
|
* Index for page to be shown first next time open() is called (only used in sequenceMode).
|
|
* @member {Number} initialPage
|
|
* @memberof OpenSeadragon.Viewer#
|
|
*/
|
|
initialPage: 0,
|
|
|
|
//dom nodes
|
|
/**
|
|
* The parent element of this Viewer instance, passed in when the Viewer was created.
|
|
* @member {Element} element
|
|
* @memberof OpenSeadragon.Viewer#
|
|
*/
|
|
element: null,
|
|
/**
|
|
* A <div> element (provided by {@link OpenSeadragon.ControlDock}), the base element of this Viewer instance.<br><br>
|
|
* Child element of {@link OpenSeadragon.Viewer#element}.
|
|
* @member {Element} container
|
|
* @memberof OpenSeadragon.Viewer#
|
|
*/
|
|
container: null,
|
|
/**
|
|
* A <div> element, the element where user-input events are handled for panning and zooming.<br><br>
|
|
* Child element of {@link OpenSeadragon.Viewer#container},
|
|
* positioned on top of {@link OpenSeadragon.Viewer#keyboardCommandArea}.<br><br>
|
|
* The parent of {@link OpenSeadragon.Drawer#canvas} instances.
|
|
* @member {Element} canvas
|
|
* @memberof OpenSeadragon.Viewer#
|
|
*/
|
|
canvas: null,
|
|
|
|
// Overlays list. An overlay allows to add html on top of the viewer.
|
|
overlays: [],
|
|
// Container inside the canvas where overlays are drawn.
|
|
overlaysContainer: null,
|
|
|
|
//private state properties
|
|
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
|
|
//in the options and is only empty by default if not specified. Also
|
|
//this array was returned from get_controls which I find confusing
|
|
//since this object has a controls property which is treated in other
|
|
//functions like clearControls. I'm removing the accessors.
|
|
customControls: [],
|
|
|
|
//These are originally not part options but declared as members
|
|
//in initialize. It's still considered idiomatic to put them here
|
|
//source is here for backwards compatibility. It is not an official
|
|
//part of the API and should not be relied upon.
|
|
source: null,
|
|
/**
|
|
* Handles rendering of tiles in the viewer. Created for each TileSource opened.
|
|
* @member {OpenSeadragon.Drawer} drawer
|
|
* @memberof OpenSeadragon.Viewer#
|
|
*/
|
|
drawer: null,
|
|
/**
|
|
* Keeps track of all of the tiled images in the scene.
|
|
* @member {OpenSeadragon.World} world
|
|
* @memberof OpenSeadragon.Viewer#
|
|
*/
|
|
world: null,
|
|
/**
|
|
* Handles coordinate-related functionality - zoom, pan, rotation, etc. Created for each TileSource opened.
|
|
* @member {OpenSeadragon.Viewport} viewport
|
|
* @memberof OpenSeadragon.Viewer#
|
|
*/
|
|
viewport: null,
|
|
/**
|
|
* @member {OpenSeadragon.Navigator} navigator
|
|
* @memberof OpenSeadragon.Viewer#
|
|
*/
|
|
navigator: null,
|
|
|
|
//A collection viewport is a separate viewport used to provide
|
|
//simultaneous rendering of sets of tiles
|
|
collectionViewport: null,
|
|
collectionDrawer: 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 );
|
|
|
|
if ( typeof ( this.hash) === "undefined" ) {
|
|
throw new Error("A hash must be defined, either by specifying options.id or options.hash.");
|
|
}
|
|
if ( typeof ( THIS[ this.hash ] ) !== "undefined" ) {
|
|
// We don't want to throw an error here, as the user might have discarded
|
|
// the previous viewer with the same hash and now want to recreate it.
|
|
$.console.warn("Hash " + this.hash + " has already been used.");
|
|
}
|
|
|
|
//Private state properties
|
|
THIS[ this.hash ] = {
|
|
fsBoundsDelta: new $.Point( 1, 1 ),
|
|
prevContainerSize: null,
|
|
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,
|
|
fullPage: false,
|
|
onfullscreenchange: null
|
|
};
|
|
|
|
this._sequenceIndex = 0;
|
|
this._firstOpen = true;
|
|
this._updateRequestId = null;
|
|
this._loadQueue = [];
|
|
this.currentOverlays = [];
|
|
this._updatePixelDensityRatioBind = null;
|
|
|
|
this._lastScrollTime = $.now(); // variable used to help normalize the scroll event speed of different devices
|
|
|
|
//Inherit some behaviors and properties
|
|
$.EventSource.call( this );
|
|
|
|
this.addHandler( 'open-failed', function ( event ) {
|
|
var msg = $.getString( "Errors.OpenFailed", event.eventSource, event.message);
|
|
_this._showMessage( msg );
|
|
});
|
|
|
|
$.ControlDock.call( this, options );
|
|
|
|
//Deal with tile sources
|
|
if (this.xmlPath) {
|
|
//Deprecated option. Now it is preferred to use the tileSources option
|
|
this.tileSources = [ this.xmlPath ];
|
|
}
|
|
|
|
this.element = this.element || document.getElementById( this.id );
|
|
this.canvas = $.makeNeutralElement( "div" );
|
|
|
|
this.canvas.className = "openseadragon-canvas";
|
|
(function( style ){
|
|
style.width = "100%";
|
|
style.height = "100%";
|
|
style.overflow = "hidden";
|
|
style.position = "absolute";
|
|
style.top = "0px";
|
|
style.left = "0px";
|
|
}(this.canvas.style));
|
|
$.setElementTouchActionNone( this.canvas );
|
|
if (options.tabIndex !== "") {
|
|
this.canvas.tabIndex = (options.tabIndex === undefined ? 0 : options.tabIndex);
|
|
}
|
|
|
|
//the container is created through applying the ControlDock constructor above
|
|
this.container.className = "openseadragon-container";
|
|
(function( style ){
|
|
style.width = "100%";
|
|
style.height = "100%";
|
|
style.position = "relative";
|
|
style.overflow = "hidden";
|
|
style.left = "0px";
|
|
style.top = "0px";
|
|
style.textAlign = "left"; // needed to protect against
|
|
}( this.container.style ));
|
|
$.setElementTouchActionNone( this.container );
|
|
|
|
this.container.insertBefore( this.canvas, this.container.firstChild );
|
|
this.element.appendChild( this.container );
|
|
|
|
//Used for toggling between fullscreen and default container size
|
|
//TODO: these can be closure private and shared across Viewer
|
|
// instances.
|
|
this.bodyWidth = document.body.style.width;
|
|
this.bodyHeight = document.body.style.height;
|
|
this.bodyOverflow = document.body.style.overflow;
|
|
this.docOverflow = document.documentElement.style.overflow;
|
|
|
|
this.innerTracker = new $.MouseTracker({
|
|
userData: 'Viewer.innerTracker',
|
|
element: this.canvas,
|
|
startDisabled: !this.mouseNavEnabled,
|
|
clickTimeThreshold: this.clickTimeThreshold,
|
|
clickDistThreshold: this.clickDistThreshold,
|
|
dblClickTimeThreshold: this.dblClickTimeThreshold,
|
|
dblClickDistThreshold: this.dblClickDistThreshold,
|
|
contextMenuHandler: $.delegate( this, onCanvasContextMenu ),
|
|
keyDownHandler: $.delegate( this, onCanvasKeyDown ),
|
|
keyHandler: $.delegate( this, onCanvasKeyPress ),
|
|
clickHandler: $.delegate( this, onCanvasClick ),
|
|
dblClickHandler: $.delegate( this, onCanvasDblClick ),
|
|
dragHandler: $.delegate( this, onCanvasDrag ),
|
|
dragEndHandler: $.delegate( this, onCanvasDragEnd ),
|
|
enterHandler: $.delegate( this, onCanvasEnter ),
|
|
leaveHandler: $.delegate( this, onCanvasLeave ),
|
|
pressHandler: $.delegate( this, onCanvasPress ),
|
|
releaseHandler: $.delegate( this, onCanvasRelease ),
|
|
nonPrimaryPressHandler: $.delegate( this, onCanvasNonPrimaryPress ),
|
|
nonPrimaryReleaseHandler: $.delegate( this, onCanvasNonPrimaryRelease ),
|
|
scrollHandler: $.delegate( this, onCanvasScroll ),
|
|
pinchHandler: $.delegate( this, onCanvasPinch )
|
|
});
|
|
|
|
this.outerTracker = new $.MouseTracker({
|
|
userData: 'Viewer.outerTracker',
|
|
element: this.container,
|
|
startDisabled: !this.mouseNavEnabled,
|
|
clickTimeThreshold: this.clickTimeThreshold,
|
|
clickDistThreshold: this.clickDistThreshold,
|
|
dblClickTimeThreshold: this.dblClickTimeThreshold,
|
|
dblClickDistThreshold: this.dblClickDistThreshold,
|
|
enterHandler: $.delegate( this, onContainerEnter ),
|
|
leaveHandler: $.delegate( this, onContainerLeave )
|
|
});
|
|
|
|
if( this.toolbar ){
|
|
this.toolbar = new $.ControlDock({ element: this.toolbar });
|
|
}
|
|
|
|
this.bindStandardControls();
|
|
|
|
THIS[ this.hash ].prevContainerSize = _getSafeElemSize( this.container );
|
|
|
|
// Create the world
|
|
this.world = new $.World({
|
|
viewer: this
|
|
});
|
|
|
|
this.world.addHandler('add-item', function(event) {
|
|
// For backwards compatibility, we maintain the source property
|
|
_this.source = _this.world.getItemAt(0).source;
|
|
|
|
THIS[ _this.hash ].forceRedraw = true;
|
|
|
|
if (!_this._updateRequestId) {
|
|
_this._updateRequestId = scheduleUpdate( _this, updateMulti );
|
|
}
|
|
});
|
|
|
|
this.world.addHandler('remove-item', function(event) {
|
|
// For backwards compatibility, we maintain the source property
|
|
if (_this.world.getItemCount()) {
|
|
_this.source = _this.world.getItemAt(0).source;
|
|
} else {
|
|
_this.source = null;
|
|
}
|
|
|
|
THIS[ _this.hash ].forceRedraw = true;
|
|
});
|
|
|
|
this.world.addHandler('metrics-change', function(event) {
|
|
if (_this.viewport) {
|
|
_this.viewport._setContentBounds(_this.world.getHomeBounds(), _this.world.getContentFactor());
|
|
}
|
|
});
|
|
|
|
this.world.addHandler('item-index-change', function(event) {
|
|
// For backwards compatibility, we maintain the source property
|
|
_this.source = _this.world.getItemAt(0).source;
|
|
});
|
|
|
|
// Create the viewport
|
|
this.viewport = new $.Viewport({
|
|
containerSize: THIS[ this.hash ].prevContainerSize,
|
|
springStiffness: this.springStiffness,
|
|
animationTime: this.animationTime,
|
|
minZoomImageRatio: this.minZoomImageRatio,
|
|
maxZoomPixelRatio: this.maxZoomPixelRatio,
|
|
visibilityRatio: this.visibilityRatio,
|
|
wrapHorizontal: this.wrapHorizontal,
|
|
wrapVertical: this.wrapVertical,
|
|
defaultZoomLevel: this.defaultZoomLevel,
|
|
minZoomLevel: this.minZoomLevel,
|
|
maxZoomLevel: this.maxZoomLevel,
|
|
viewer: this,
|
|
degrees: this.degrees,
|
|
flipped: this.flipped,
|
|
navigatorRotate: this.navigatorRotate,
|
|
homeFillsViewer: this.homeFillsViewer,
|
|
margins: this.viewportMargins,
|
|
silenceMultiImageWarnings: this.silenceMultiImageWarnings
|
|
});
|
|
|
|
this.viewport._setContentBounds(this.world.getHomeBounds(), this.world.getContentFactor());
|
|
|
|
// Create the image loader
|
|
this.imageLoader = new $.ImageLoader({
|
|
jobLimit: this.imageLoaderLimit,
|
|
timeout: options.timeout
|
|
});
|
|
|
|
// Create the tile cache
|
|
this.tileCache = new $.TileCache({
|
|
maxImageCacheCount: this.maxImageCacheCount
|
|
});
|
|
|
|
// Create the drawer
|
|
this.drawer = new $.Drawer({
|
|
viewer: this,
|
|
viewport: this.viewport,
|
|
element: this.canvas,
|
|
debugGridColor: this.debugGridColor
|
|
});
|
|
|
|
// Overlay container
|
|
this.overlaysContainer = $.makeNeutralElement( "div" );
|
|
this.canvas.appendChild( this.overlaysContainer );
|
|
|
|
// Now that we have a drawer, see if it supports rotate. If not we need to remove the rotate buttons
|
|
if (!this.drawer.canRotate()) {
|
|
// Disable/remove the rotate left/right buttons since they aren't supported
|
|
if (this.rotateLeft) {
|
|
i = this.buttonGroup.buttons.indexOf(this.rotateLeft);
|
|
this.buttonGroup.buttons.splice(i, 1);
|
|
this.buttonGroup.element.removeChild(this.rotateLeft.element);
|
|
}
|
|
if (this.rotateRight) {
|
|
i = this.buttonGroup.buttons.indexOf(this.rotateRight);
|
|
this.buttonGroup.buttons.splice(i, 1);
|
|
this.buttonGroup.element.removeChild(this.rotateRight.element);
|
|
}
|
|
}
|
|
|
|
this._addUpdatePixelDensityRatioEvent();
|
|
|
|
//Instantiate a navigator if configured
|
|
if ( this.showNavigator){
|
|
this.navigator = new $.Navigator({
|
|
id: this.navigatorId,
|
|
position: this.navigatorPosition,
|
|
sizeRatio: this.navigatorSizeRatio,
|
|
maintainSizeRatio: this.navigatorMaintainSizeRatio,
|
|
top: this.navigatorTop,
|
|
left: this.navigatorLeft,
|
|
width: this.navigatorWidth,
|
|
height: this.navigatorHeight,
|
|
autoResize: this.navigatorAutoResize,
|
|
autoFade: this.navigatorAutoFade,
|
|
prefixUrl: this.prefixUrl,
|
|
viewer: this,
|
|
navigatorRotate: this.navigatorRotate,
|
|
background: this.navigatorBackground,
|
|
opacity: this.navigatorOpacity,
|
|
borderColor: this.navigatorBorderColor,
|
|
displayRegionColor: this.navigatorDisplayRegionColor,
|
|
crossOriginPolicy: this.crossOriginPolicy
|
|
});
|
|
}
|
|
|
|
// Sequence mode
|
|
if (this.sequenceMode) {
|
|
this.bindSequenceControls();
|
|
}
|
|
|
|
// Open initial tilesources
|
|
if (this.tileSources) {
|
|
this.open( this.tileSources );
|
|
}
|
|
|
|
// Add custom controls
|
|
for ( i = 0; i < this.customControls.length; i++ ) {
|
|
this.addControl(
|
|
this.customControls[ i ].id,
|
|
{anchor: this.customControls[ i ].anchor}
|
|
);
|
|
}
|
|
|
|
// Initial fade out
|
|
$.requestAnimationFrame( function(){
|
|
beginControlsAutoHide( _this );
|
|
} );
|
|
|
|
// Initial canvas options
|
|
if ( this.imageSmoothingEnabled !== undefined && !this.imageSmoothingEnabled){
|
|
this.drawer.setImageSmoothingEnabled(this.imageSmoothingEnabled);
|
|
}
|
|
|
|
// Register the viewer
|
|
$._viewers.set(this.element, this);
|
|
};
|
|
|
|
$.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, /** @lends OpenSeadragon.Viewer.prototype */{
|
|
|
|
|
|
/**
|
|
* @function
|
|
* @return {Boolean}
|
|
*/
|
|
isOpen: function () {
|
|
return !!this.world.getItemCount();
|
|
},
|
|
|
|
// deprecated
|
|
openDzi: function ( dzi ) {
|
|
$.console.error( "[Viewer.openDzi] this function is deprecated; use Viewer.open() instead." );
|
|
return this.open( dzi );
|
|
},
|
|
|
|
// deprecated
|
|
openTileSource: function ( tileSource ) {
|
|
$.console.error( "[Viewer.openTileSource] this function is deprecated; use Viewer.open() instead." );
|
|
return this.open( tileSource );
|
|
},
|
|
|
|
//deprecated
|
|
get buttons () {
|
|
$.console.warn('Viewer.buttons is deprecated; Please use Viewer.buttonGroup');
|
|
return this.buttonGroup;
|
|
},
|
|
|
|
/**
|
|
* Open tiled images into the viewer, closing any others.
|
|
* To get the TiledImage instance created by open, add an event listener for
|
|
* {@link OpenSeadragon.Viewer.html#.event:open}, which when fired can be used to get access
|
|
* to the instance, i.e., viewer.world.getItemAt(0).
|
|
* @function
|
|
* @param {Array|String|Object|Function} tileSources - This can be a TiledImage
|
|
* specifier, a TileSource specifier, or an array of either. A TiledImage specifier
|
|
* is the same as the options parameter for {@link OpenSeadragon.Viewer#addTiledImage},
|
|
* except for the index property; images are added in sequence.
|
|
* A TileSource specifier is anything you could pass as the tileSource property
|
|
* of the options parameter for {@link OpenSeadragon.Viewer#addTiledImage}.
|
|
* @param {Number} initialPage - If sequenceMode is true, display this page initially
|
|
* for the given tileSources. If specified, will overwrite the Viewer's existing initialPage property.
|
|
* @return {OpenSeadragon.Viewer} Chainable.
|
|
* @fires OpenSeadragon.Viewer.event:open
|
|
* @fires OpenSeadragon.Viewer.event:open-failed
|
|
*/
|
|
open: function (tileSources, initialPage) {
|
|
var _this = this;
|
|
|
|
this.close();
|
|
|
|
if (!tileSources) {
|
|
return this;
|
|
}
|
|
|
|
if (this.sequenceMode && $.isArray(tileSources)) {
|
|
if (this.referenceStrip) {
|
|
this.referenceStrip.destroy();
|
|
this.referenceStrip = null;
|
|
}
|
|
|
|
if (typeof initialPage !== 'undefined' && !isNaN(initialPage)) {
|
|
this.initialPage = initialPage;
|
|
}
|
|
|
|
this.tileSources = tileSources;
|
|
this._sequenceIndex = Math.max(0, Math.min(this.tileSources.length - 1, this.initialPage));
|
|
if (this.tileSources.length) {
|
|
this.open(this.tileSources[this._sequenceIndex]);
|
|
|
|
if ( this.showReferenceStrip ){
|
|
this.addReferenceStrip();
|
|
}
|
|
}
|
|
|
|
this._updateSequenceButtons( this._sequenceIndex );
|
|
return this;
|
|
}
|
|
|
|
if (!$.isArray(tileSources)) {
|
|
tileSources = [tileSources];
|
|
}
|
|
|
|
if (!tileSources.length) {
|
|
return this;
|
|
}
|
|
|
|
this._opening = true;
|
|
|
|
var expected = tileSources.length;
|
|
var successes = 0;
|
|
var failures = 0;
|
|
var failEvent;
|
|
|
|
var checkCompletion = function() {
|
|
if (successes + failures === expected) {
|
|
if (successes) {
|
|
if (_this._firstOpen || !_this.preserveViewport) {
|
|
_this.viewport.goHome( true );
|
|
_this.viewport.update();
|
|
}
|
|
|
|
_this._firstOpen = false;
|
|
|
|
var source = tileSources[0];
|
|
if (source.tileSource) {
|
|
source = source.tileSource;
|
|
}
|
|
|
|
// Global overlays
|
|
if( _this.overlays && !_this.preserveOverlays ){
|
|
for ( var i = 0; i < _this.overlays.length; i++ ) {
|
|
_this.currentOverlays[ i ] = getOverlayObject( _this, _this.overlays[ i ] );
|
|
}
|
|
}
|
|
|
|
_this._drawOverlays();
|
|
_this._opening = false;
|
|
|
|
/**
|
|
* Raised when the viewer has opened and loaded one or more TileSources.
|
|
*
|
|
* @event open
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
|
* @property {OpenSeadragon.TileSource} source - The tile source that was opened.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
// TODO: what if there are multiple sources?
|
|
_this.raiseEvent( 'open', { source: source } );
|
|
} else {
|
|
_this._opening = false;
|
|
|
|
/**
|
|
* Raised when an error occurs loading a TileSource.
|
|
*
|
|
* @event open-failed
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
|
* @property {String} message - Information about what failed.
|
|
* @property {String} source - The tile source that failed.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
_this.raiseEvent( 'open-failed', failEvent );
|
|
}
|
|
}
|
|
};
|
|
|
|
var doOne = function(options) {
|
|
if (!$.isPlainObject(options) || !options.tileSource) {
|
|
options = {
|
|
tileSource: options
|
|
};
|
|
}
|
|
|
|
if (options.index !== undefined) {
|
|
$.console.error('[Viewer.open] setting indexes here is not supported; use addTiledImage instead');
|
|
delete options.index;
|
|
}
|
|
|
|
if (options.collectionImmediately === undefined) {
|
|
options.collectionImmediately = true;
|
|
}
|
|
|
|
var originalSuccess = options.success;
|
|
options.success = function(event) {
|
|
successes++;
|
|
|
|
// TODO: now that options has other things besides tileSource, the overlays
|
|
// should probably be at the options level, not the tileSource level.
|
|
if (options.tileSource.overlays) {
|
|
for (var i = 0; i < options.tileSource.overlays.length; i++) {
|
|
_this.addOverlay(options.tileSource.overlays[i]);
|
|
}
|
|
}
|
|
|
|
if (originalSuccess) {
|
|
originalSuccess(event);
|
|
}
|
|
|
|
checkCompletion();
|
|
};
|
|
|
|
var originalError = options.error;
|
|
options.error = function(event) {
|
|
failures++;
|
|
|
|
if (!failEvent) {
|
|
failEvent = event;
|
|
}
|
|
|
|
if (originalError) {
|
|
originalError(event);
|
|
}
|
|
|
|
checkCompletion();
|
|
};
|
|
|
|
_this.addTiledImage(options);
|
|
};
|
|
|
|
// TileSources
|
|
for (var i = 0; i < tileSources.length; i++) {
|
|
doOne(tileSources[i]);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
|
|
/**
|
|
* @function
|
|
* @return {OpenSeadragon.Viewer} Chainable.
|
|
* @fires OpenSeadragon.Viewer.event:close
|
|
*/
|
|
close: function ( ) {
|
|
if ( !THIS[ this.hash ] ) {
|
|
//this viewer has already been destroyed: returning immediately
|
|
return this;
|
|
}
|
|
|
|
this._opening = false;
|
|
|
|
if ( this.navigator ) {
|
|
this.navigator.close();
|
|
}
|
|
|
|
if (!this.preserveOverlays) {
|
|
this.clearOverlays();
|
|
this.overlaysContainer.innerHTML = "";
|
|
}
|
|
|
|
THIS[ this.hash ].animating = false;
|
|
|
|
this.world.removeAll();
|
|
this.imageLoader.clear();
|
|
|
|
/**
|
|
* Raised when the viewer is closed (see {@link OpenSeadragon.Viewer#close}).
|
|
*
|
|
* @event close
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'close' );
|
|
|
|
return this;
|
|
},
|
|
|
|
|
|
/**
|
|
* Function to destroy the viewer and clean up everything created by OpenSeadragon.
|
|
*
|
|
* Example:
|
|
* var viewer = OpenSeadragon({
|
|
* [...]
|
|
* });
|
|
*
|
|
* //when you are done with the viewer:
|
|
* viewer.destroy();
|
|
* viewer = null; //important
|
|
*
|
|
* @function
|
|
*/
|
|
destroy: function( ) {
|
|
if ( !THIS[ this.hash ] ) {
|
|
//this viewer has already been destroyed: returning immediately
|
|
return;
|
|
}
|
|
|
|
this._removeUpdatePixelDensityRatioEvent();
|
|
|
|
this.close();
|
|
|
|
this.clearOverlays();
|
|
this.overlaysContainer.innerHTML = "";
|
|
|
|
//TODO: implement this...
|
|
//this.unbindSequenceControls()
|
|
//this.unbindStandardControls()
|
|
|
|
if (this.referenceStrip) {
|
|
this.referenceStrip.destroy();
|
|
this.referenceStrip = null;
|
|
}
|
|
|
|
if ( this._updateRequestId !== null ) {
|
|
$.cancelAnimationFrame( this._updateRequestId );
|
|
this._updateRequestId = null;
|
|
}
|
|
|
|
if ( this.drawer ) {
|
|
this.drawer.destroy();
|
|
}
|
|
|
|
if ( this.navigator ) {
|
|
this.navigator.destroy();
|
|
THIS[ this.navigator.hash ] = null;
|
|
delete THIS[ this.navigator.hash ];
|
|
this.navigator = null;
|
|
}
|
|
|
|
this.removeAllHandlers();
|
|
|
|
if (this.buttonGroup) {
|
|
this.buttonGroup.destroy();
|
|
} else if (this.customButtons) {
|
|
while (this.customButtons.length) {
|
|
this.customButtons.pop().destroy();
|
|
}
|
|
}
|
|
|
|
if (this.paging) {
|
|
this.paging.destroy();
|
|
}
|
|
|
|
// Go through top element (passed to us) and remove all children
|
|
// Use removeChild to make sure it handles SVG or any non-html
|
|
// also it performs better - http://jsperf.com/innerhtml-vs-removechild/15
|
|
if (this.element){
|
|
while (this.element.firstChild) {
|
|
this.element.removeChild(this.element.firstChild);
|
|
}
|
|
}
|
|
|
|
this.container.onsubmit = null;
|
|
this.clearControls();
|
|
|
|
// destroy the mouse trackers
|
|
if (this.innerTracker){
|
|
this.innerTracker.destroy();
|
|
}
|
|
if (this.outerTracker){
|
|
this.outerTracker.destroy();
|
|
}
|
|
|
|
THIS[ this.hash ] = null;
|
|
delete THIS[ this.hash ];
|
|
|
|
// clear all our references to dom objects
|
|
this.canvas = null;
|
|
this.container = null;
|
|
|
|
// Unregister the viewer
|
|
$._viewers.delete(this.element);
|
|
|
|
// clear our reference to the main element - they will need to pass it in again, creating a new viewer
|
|
this.element = null;
|
|
},
|
|
|
|
/**
|
|
* @function
|
|
* @return {Boolean}
|
|
*/
|
|
isMouseNavEnabled: function () {
|
|
return this.innerTracker.isTracking();
|
|
},
|
|
|
|
/**
|
|
* @function
|
|
* @param {Boolean} enabled - true to enable, false to disable
|
|
* @return {OpenSeadragon.Viewer} Chainable.
|
|
* @fires OpenSeadragon.Viewer.event:mouse-enabled
|
|
*/
|
|
setMouseNavEnabled: function( enabled ){
|
|
this.innerTracker.setTracking( enabled );
|
|
this.outerTracker.setTracking( enabled );
|
|
/**
|
|
* Raised when mouse/touch navigation is enabled or disabled (see {@link OpenSeadragon.Viewer#setMouseNavEnabled}).
|
|
*
|
|
* @event mouse-enabled
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
|
* @property {Boolean} enabled
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'mouse-enabled', { enabled: enabled } );
|
|
return this;
|
|
},
|
|
|
|
|
|
/**
|
|
* @function
|
|
* @return {Boolean}
|
|
*/
|
|
areControlsEnabled: function () {
|
|
var enabled = this.controls.length,
|
|
i;
|
|
for( i = 0; i < this.controls.length; i++ ){
|
|
enabled = enabled && this.controls[ i ].isVisible();
|
|
}
|
|
return enabled;
|
|
},
|
|
|
|
|
|
/**
|
|
* Shows or hides the controls (e.g. the default navigation buttons).
|
|
*
|
|
* @function
|
|
* @param {Boolean} true to show, false to hide.
|
|
* @return {OpenSeadragon.Viewer} Chainable.
|
|
* @fires OpenSeadragon.Viewer.event:controls-enabled
|
|
*/
|
|
setControlsEnabled: function( enabled ) {
|
|
if( enabled ){
|
|
abortControlsAutoHide( this );
|
|
} else {
|
|
beginControlsAutoHide( this );
|
|
}
|
|
/**
|
|
* Raised when the navigation controls are shown or hidden (see {@link OpenSeadragon.Viewer#setControlsEnabled}).
|
|
*
|
|
* @event controls-enabled
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
|
* @property {Boolean} enabled
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'controls-enabled', { enabled: enabled } );
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Turns debugging mode on or off for this viewer.
|
|
*
|
|
* @function
|
|
* @param {Boolean} true to turn debug on, false to turn debug off.
|
|
*/
|
|
setDebugMode: function(debugMode){
|
|
|
|
for (var i = 0; i < this.world.getItemCount(); i++) {
|
|
this.world.getItemAt(i).debugMode = debugMode;
|
|
}
|
|
|
|
this.debugMode = debugMode;
|
|
this.forceRedraw();
|
|
},
|
|
|
|
/**
|
|
* @function
|
|
* @return {Boolean}
|
|
*/
|
|
isFullPage: function () {
|
|
return THIS[ this.hash ].fullPage;
|
|
},
|
|
|
|
|
|
/**
|
|
* Toggle full page mode.
|
|
* @function
|
|
* @param {Boolean} fullPage
|
|
* If true, enter full page mode. If false, exit full page mode.
|
|
* @return {OpenSeadragon.Viewer} Chainable.
|
|
* @fires OpenSeadragon.Viewer.event:pre-full-page
|
|
* @fires OpenSeadragon.Viewer.event:full-page
|
|
*/
|
|
setFullPage: function( fullPage ) {
|
|
|
|
var body = document.body,
|
|
bodyStyle = body.style,
|
|
docStyle = document.documentElement.style,
|
|
_this = this,
|
|
nodes,
|
|
i;
|
|
|
|
//don't bother modifying the DOM if we are already in full page mode.
|
|
if ( fullPage === this.isFullPage() ) {
|
|
return this;
|
|
}
|
|
|
|
var fullPageEventArgs = {
|
|
fullPage: fullPage,
|
|
preventDefaultAction: false
|
|
};
|
|
/**
|
|
* Raised when the viewer is about to change to/from full-page mode (see {@link OpenSeadragon.Viewer#setFullPage}).
|
|
*
|
|
* @event pre-full-page
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
|
* @property {Boolean} fullPage - True if entering full-page mode, false if exiting full-page mode.
|
|
* @property {Boolean} preventDefaultAction - Set to true to prevent full-page mode change. Default: false.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'pre-full-page', fullPageEventArgs );
|
|
if ( fullPageEventArgs.preventDefaultAction ) {
|
|
return this;
|
|
}
|
|
|
|
if ( fullPage ) {
|
|
|
|
this.elementSize = $.getElementSize( this.element );
|
|
this.pageScroll = $.getPageScroll();
|
|
|
|
this.elementMargin = this.element.style.margin;
|
|
this.element.style.margin = "0";
|
|
this.elementPadding = this.element.style.padding;
|
|
this.element.style.padding = "0";
|
|
|
|
this.bodyMargin = bodyStyle.margin;
|
|
this.docMargin = docStyle.margin;
|
|
bodyStyle.margin = "0";
|
|
docStyle.margin = "0";
|
|
|
|
this.bodyPadding = bodyStyle.padding;
|
|
this.docPadding = docStyle.padding;
|
|
bodyStyle.padding = "0";
|
|
docStyle.padding = "0";
|
|
|
|
this.bodyWidth = bodyStyle.width;
|
|
this.docWidth = docStyle.width;
|
|
bodyStyle.width = "100%";
|
|
docStyle.width = "100%";
|
|
|
|
this.bodyHeight = bodyStyle.height;
|
|
this.docHeight = docStyle.height;
|
|
bodyStyle.height = "100%";
|
|
docStyle.height = "100%";
|
|
|
|
this.bodyDisplay = bodyStyle.display;
|
|
bodyStyle.display = "block";
|
|
|
|
//when entering full screen on the ipad it wasn't sufficient to leave
|
|
//the body intact as only only the top half of the screen would
|
|
//respond to touch events on the canvas, while the bottom half treated
|
|
//them as touch events on the document body. Thus we remove and store
|
|
//the bodies elements and replace them when we leave full screen.
|
|
this.previousBody = [];
|
|
THIS[ this.hash ].prevElementParent = this.element.parentNode;
|
|
THIS[ this.hash ].prevNextSibling = this.element.nextSibling;
|
|
THIS[ this.hash ].prevElementWidth = this.element.style.width;
|
|
THIS[ this.hash ].prevElementHeight = this.element.style.height;
|
|
nodes = body.childNodes.length;
|
|
for ( i = 0; i < nodes; i++ ) {
|
|
this.previousBody.push( body.childNodes[ 0 ] );
|
|
body.removeChild( body.childNodes[ 0 ] );
|
|
}
|
|
|
|
//If we've got a toolbar, we need to enable the user to use css to
|
|
//preserve it in fullpage mode
|
|
if ( this.toolbar && this.toolbar.element ) {
|
|
//save a reference to the parent so we can put it back
|
|
//in the long run we need a better strategy
|
|
this.toolbar.parentNode = this.toolbar.element.parentNode;
|
|
this.toolbar.nextSibling = this.toolbar.element.nextSibling;
|
|
body.appendChild( this.toolbar.element );
|
|
|
|
//Make sure the user has some ability to style the toolbar based
|
|
//on the mode
|
|
$.addClass( this.toolbar.element, 'fullpage' );
|
|
}
|
|
|
|
$.addClass( this.element, 'fullpage' );
|
|
body.appendChild( this.element );
|
|
|
|
this.element.style.height = '100vh';
|
|
this.element.style.width = '100vw';
|
|
|
|
if ( this.toolbar && this.toolbar.element ) {
|
|
this.element.style.height = (
|
|
$.getElementSize( this.element ).y - $.getElementSize( this.toolbar.element ).y
|
|
) + 'px';
|
|
}
|
|
|
|
THIS[ this.hash ].fullPage = true;
|
|
|
|
// mouse will be inside container now
|
|
$.delegate( this, onContainerEnter )( {} );
|
|
|
|
} else {
|
|
|
|
this.element.style.margin = this.elementMargin;
|
|
this.element.style.padding = this.elementPadding;
|
|
|
|
bodyStyle.margin = this.bodyMargin;
|
|
docStyle.margin = this.docMargin;
|
|
|
|
bodyStyle.padding = this.bodyPadding;
|
|
docStyle.padding = this.docPadding;
|
|
|
|
bodyStyle.width = this.bodyWidth;
|
|
docStyle.width = this.docWidth;
|
|
|
|
bodyStyle.height = this.bodyHeight;
|
|
docStyle.height = this.docHeight;
|
|
|
|
bodyStyle.display = this.bodyDisplay;
|
|
|
|
body.removeChild( this.element );
|
|
nodes = this.previousBody.length;
|
|
for ( i = 0; i < nodes; i++ ) {
|
|
body.appendChild( this.previousBody.shift() );
|
|
}
|
|
|
|
$.removeClass( this.element, 'fullpage' );
|
|
THIS[ this.hash ].prevElementParent.insertBefore(
|
|
this.element,
|
|
THIS[ this.hash ].prevNextSibling
|
|
);
|
|
|
|
//If we've got a toolbar, we need to enable the user to use css to
|
|
//reset it to its original state
|
|
if ( this.toolbar && this.toolbar.element ) {
|
|
body.removeChild( this.toolbar.element );
|
|
|
|
//Make sure the user has some ability to style the toolbar based
|
|
//on the mode
|
|
$.removeClass( this.toolbar.element, 'fullpage' );
|
|
|
|
this.toolbar.parentNode.insertBefore(
|
|
this.toolbar.element,
|
|
this.toolbar.nextSibling
|
|
);
|
|
delete this.toolbar.parentNode;
|
|
delete this.toolbar.nextSibling;
|
|
}
|
|
|
|
this.element.style.width = THIS[ this.hash ].prevElementWidth;
|
|
this.element.style.height = THIS[ this.hash ].prevElementHeight;
|
|
|
|
// After exiting fullPage or fullScreen, it can take some time
|
|
// before the browser can actually set the scroll.
|
|
var restoreScrollCounter = 0;
|
|
var restoreScroll = function() {
|
|
$.setPageScroll( _this.pageScroll );
|
|
var pageScroll = $.getPageScroll();
|
|
restoreScrollCounter++;
|
|
if (restoreScrollCounter < 10 &&
|
|
(pageScroll.x !== _this.pageScroll.x ||
|
|
pageScroll.y !== _this.pageScroll.y)) {
|
|
$.requestAnimationFrame( restoreScroll );
|
|
}
|
|
};
|
|
$.requestAnimationFrame( restoreScroll );
|
|
|
|
THIS[ this.hash ].fullPage = false;
|
|
|
|
// mouse will likely be outside now
|
|
$.delegate( this, onContainerLeave )( { } );
|
|
|
|
}
|
|
|
|
if ( this.navigator && this.viewport ) {
|
|
this.navigator.update( this.viewport );
|
|
}
|
|
|
|
/**
|
|
* Raised when the viewer has changed to/from full-page mode (see {@link OpenSeadragon.Viewer#setFullPage}).
|
|
*
|
|
* @event full-page
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
|
* @property {Boolean} fullPage - True if changed to full-page mode, false if exited full-page mode.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'full-page', { fullPage: fullPage } );
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Toggle full screen mode if supported. Toggle full page mode otherwise.
|
|
* @function
|
|
* @param {Boolean} fullScreen
|
|
* If true, enter full screen mode. If false, exit full screen mode.
|
|
* @return {OpenSeadragon.Viewer} Chainable.
|
|
* @fires OpenSeadragon.Viewer.event:pre-full-screen
|
|
* @fires OpenSeadragon.Viewer.event:full-screen
|
|
*/
|
|
setFullScreen: function( fullScreen ) {
|
|
var _this = this;
|
|
|
|
if ( !$.supportsFullScreen ) {
|
|
return this.setFullPage( fullScreen );
|
|
}
|
|
|
|
if ( $.isFullScreen() === fullScreen ) {
|
|
return this;
|
|
}
|
|
|
|
var fullScreeEventArgs = {
|
|
fullScreen: fullScreen,
|
|
preventDefaultAction: false
|
|
};
|
|
/**
|
|
* Raised when the viewer is about to change to/from full-screen mode (see {@link OpenSeadragon.Viewer#setFullScreen}).
|
|
* Note: the pre-full-screen event is not raised when the user is exiting
|
|
* full-screen mode by pressing the Esc key. In that case, consider using
|
|
* the full-screen, pre-full-page or full-page events.
|
|
*
|
|
* @event pre-full-screen
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
|
* @property {Boolean} fullScreen - True if entering full-screen mode, false if exiting full-screen mode.
|
|
* @property {Boolean} preventDefaultAction - Set to true to prevent full-screen mode change. Default: false.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'pre-full-screen', fullScreeEventArgs );
|
|
if ( fullScreeEventArgs.preventDefaultAction ) {
|
|
return this;
|
|
}
|
|
|
|
if ( fullScreen ) {
|
|
|
|
this.setFullPage( true );
|
|
// If the full page mode is not actually entered, we need to prevent
|
|
// the full screen mode.
|
|
if ( !this.isFullPage() ) {
|
|
return this;
|
|
}
|
|
|
|
this.fullPageStyleWidth = this.element.style.width;
|
|
this.fullPageStyleHeight = this.element.style.height;
|
|
this.element.style.width = '100%';
|
|
this.element.style.height = '100%';
|
|
|
|
var onFullScreenChange = function() {
|
|
var isFullScreen = $.isFullScreen();
|
|
if ( !isFullScreen ) {
|
|
$.removeEvent( document, $.fullScreenEventName, onFullScreenChange );
|
|
$.removeEvent( document, $.fullScreenErrorEventName, onFullScreenChange );
|
|
|
|
_this.setFullPage( false );
|
|
if ( _this.isFullPage() ) {
|
|
_this.element.style.width = _this.fullPageStyleWidth;
|
|
_this.element.style.height = _this.fullPageStyleHeight;
|
|
}
|
|
}
|
|
if ( _this.navigator && _this.viewport ) {
|
|
//09/08/2018 - Fabroh : Fix issue #1504 : Ensure to get the navigator updated on fullscreen out with custom location with a timeout
|
|
setTimeout(function(){
|
|
_this.navigator.update( _this.viewport );
|
|
});
|
|
}
|
|
/**
|
|
* Raised when the viewer has changed to/from full-screen mode (see {@link OpenSeadragon.Viewer#setFullScreen}).
|
|
*
|
|
* @event full-screen
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
|
* @property {Boolean} fullScreen - True if changed to full-screen mode, false if exited full-screen mode.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
_this.raiseEvent( 'full-screen', { fullScreen: isFullScreen } );
|
|
};
|
|
$.addEvent( document, $.fullScreenEventName, onFullScreenChange );
|
|
$.addEvent( document, $.fullScreenErrorEventName, onFullScreenChange );
|
|
|
|
$.requestFullScreen( document.body );
|
|
|
|
} else {
|
|
$.exitFullScreen();
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* @function
|
|
* @return {Boolean}
|
|
*/
|
|
isVisible: function () {
|
|
return this.container.style.visibility !== "hidden";
|
|
},
|
|
|
|
|
|
//
|
|
/**
|
|
* @function
|
|
* @returns {Boolean} returns true if the viewer is in fullscreen
|
|
*/
|
|
isFullScreen: function () {
|
|
return $.isFullScreen() && this.isFullPage();
|
|
},
|
|
|
|
/**
|
|
* @function
|
|
* @param {Boolean} visible
|
|
* @return {OpenSeadragon.Viewer} Chainable.
|
|
* @fires OpenSeadragon.Viewer.event:visible
|
|
*/
|
|
setVisible: function( visible ){
|
|
this.container.style.visibility = visible ? "" : "hidden";
|
|
/**
|
|
* Raised when the viewer is shown or hidden (see {@link OpenSeadragon.Viewer#setVisible}).
|
|
*
|
|
* @event visible
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
|
* @property {Boolean} visible
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'visible', { visible: visible } );
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Add a tiled image to the viewer.
|
|
* options.tileSource can be anything that {@link OpenSeadragon.Viewer#open}
|
|
* supports except arrays of images.
|
|
* Note that you can specify options.width or options.height, but not both.
|
|
* The other dimension will be calculated according to the item's aspect ratio.
|
|
* If collectionMode is on (see {@link OpenSeadragon.Options}), the new image is
|
|
* automatically arranged with the others.
|
|
* @function
|
|
* @param {Object} options
|
|
* @param {String|Object|Function} options.tileSource - The TileSource specifier.
|
|
* A String implies a url used to determine the tileSource implementation
|
|
* based on the file extension of url. JSONP is implied by *.js,
|
|
* otherwise the url is retrieved as text and the resulting text is
|
|
* introspected to determine if its json, xml, or text and parsed.
|
|
* An Object implies an inline configuration which has a single
|
|
* property sufficient for being able to determine tileSource
|
|
* implementation. If the object has a property which is a function
|
|
* named 'getTileUrl', it is treated as a custom TileSource.
|
|
* @param {Number} [options.index] The index of the item. Added on top of
|
|
* all other items if not specified.
|
|
* @param {Boolean} [options.replace=false] If true, the item at options.index will be
|
|
* removed and the new item is added in its place. options.tileSource will be
|
|
* interpreted and fetched if necessary before the old item is removed to avoid leaving
|
|
* a gap in the world.
|
|
* @param {Number} [options.x=0] The X position for the image in viewport coordinates.
|
|
* @param {Number} [options.y=0] The Y position for the image in viewport coordinates.
|
|
* @param {Number} [options.width=1] The width for the image in viewport coordinates.
|
|
* @param {Number} [options.height] The height for the image in viewport coordinates.
|
|
* @param {OpenSeadragon.Rect} [options.fitBounds] The bounds in viewport coordinates
|
|
* to fit the image into. If specified, x, y, width and height get ignored.
|
|
* @param {OpenSeadragon.Placement} [options.fitBoundsPlacement=OpenSeadragon.Placement.CENTER]
|
|
* How to anchor the image in the bounds if options.fitBounds is set.
|
|
* @param {OpenSeadragon.Rect} [options.clip] - An area, in image pixels, to clip to
|
|
* (portions of the image outside of this area will not be visible). Only works on
|
|
* browsers that support the HTML5 canvas.
|
|
* @param {Number} [options.opacity=1] Proportional opacity of the tiled images (1=opaque, 0=hidden)
|
|
* @param {Boolean} [options.preload=false] Default switch for loading hidden images (true loads, false blocks)
|
|
* @param {Number} [options.degrees=0] Initial rotation of the tiled image around
|
|
* its top left corner in degrees.
|
|
* @param {Boolean} [options.flipped=false] Whether to horizontally flip the image.
|
|
* @param {String} [options.compositeOperation] How the image is composited onto other images.
|
|
* @param {String} [options.crossOriginPolicy] The crossOriginPolicy for this specific image,
|
|
* overriding viewer.crossOriginPolicy.
|
|
* @param {Boolean} [options.ajaxWithCredentials] Whether to set withCredentials on tile AJAX
|
|
* @param {Boolean} [options.loadTilesWithAjax]
|
|
* Whether to load tile data using AJAX requests.
|
|
* Defaults to the setting in {@link OpenSeadragon.Options}.
|
|
* @param {Object} [options.ajaxHeaders]
|
|
* A set of headers to include when making tile AJAX requests.
|
|
* Note that these headers will be merged over any headers specified in {@link OpenSeadragon.Options}.
|
|
* Specifying a falsy value for a header will clear its existing value set at the Viewer level (if any).
|
|
* requests.
|
|
* @param {Function} [options.success] A function that gets called when the image is
|
|
* successfully added. It's passed the event object which contains a single property:
|
|
* "item", which is the resulting instance of TiledImage.
|
|
* @param {Function} [options.error] A function that gets called if the image is
|
|
* unable to be added. It's passed the error event object, which contains "message"
|
|
* and "source" properties.
|
|
* @param {Boolean} [options.collectionImmediately=false] If collectionMode is on,
|
|
* specifies whether to snap to the new arrangement immediately or to animate to it.
|
|
* @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}.
|
|
* @fires OpenSeadragon.World.event:add-item
|
|
* @fires OpenSeadragon.Viewer.event:add-item-failed
|
|
*/
|
|
addTiledImage: function( options ) {
|
|
$.console.assert(options, "[Viewer.addTiledImage] options is required");
|
|
$.console.assert(options.tileSource, "[Viewer.addTiledImage] options.tileSource is required");
|
|
$.console.assert(!options.replace || (options.index > -1 && options.index < this.world.getItemCount()),
|
|
"[Viewer.addTiledImage] if options.replace is used, options.index must be a valid index in Viewer.world");
|
|
|
|
var _this = this;
|
|
|
|
if (options.replace) {
|
|
options.replaceItem = _this.world.getItemAt(options.index);
|
|
}
|
|
|
|
this._hideMessage();
|
|
|
|
if (options.placeholderFillStyle === undefined) {
|
|
options.placeholderFillStyle = this.placeholderFillStyle;
|
|
}
|
|
if (options.opacity === undefined) {
|
|
options.opacity = this.opacity;
|
|
}
|
|
if (options.preload === undefined) {
|
|
options.preload = this.preload;
|
|
}
|
|
if (options.compositeOperation === undefined) {
|
|
options.compositeOperation = this.compositeOperation;
|
|
}
|
|
if (options.crossOriginPolicy === undefined) {
|
|
options.crossOriginPolicy = options.tileSource.crossOriginPolicy !== undefined ? options.tileSource.crossOriginPolicy : this.crossOriginPolicy;
|
|
}
|
|
if (options.ajaxWithCredentials === undefined) {
|
|
options.ajaxWithCredentials = this.ajaxWithCredentials;
|
|
}
|
|
if (options.loadTilesWithAjax === undefined) {
|
|
options.loadTilesWithAjax = this.loadTilesWithAjax;
|
|
}
|
|
if (options.ajaxHeaders === undefined || options.ajaxHeaders === null) {
|
|
options.ajaxHeaders = this.ajaxHeaders;
|
|
} else if ($.isPlainObject(options.ajaxHeaders) && $.isPlainObject(this.ajaxHeaders)) {
|
|
options.ajaxHeaders = $.extend({}, this.ajaxHeaders, options.ajaxHeaders);
|
|
}
|
|
|
|
var myQueueItem = {
|
|
options: options
|
|
};
|
|
|
|
function raiseAddItemFailed( event ) {
|
|
for (var i = 0; i < _this._loadQueue.length; i++) {
|
|
if (_this._loadQueue[i] === myQueueItem) {
|
|
_this._loadQueue.splice(i, 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (_this._loadQueue.length === 0) {
|
|
refreshWorld(myQueueItem);
|
|
}
|
|
|
|
/**
|
|
* Raised when an error occurs while adding a item.
|
|
* @event add-item-failed
|
|
* @memberOf OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
|
* @property {String} message
|
|
* @property {String} source
|
|
* @property {Object} options The options passed to the addTiledImage method.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
_this.raiseEvent( 'add-item-failed', event );
|
|
|
|
if (options.error) {
|
|
options.error(event);
|
|
}
|
|
}
|
|
|
|
function refreshWorld(theItem) {
|
|
if (_this.collectionMode) {
|
|
_this.world.arrange({
|
|
immediately: theItem.options.collectionImmediately,
|
|
rows: _this.collectionRows,
|
|
columns: _this.collectionColumns,
|
|
layout: _this.collectionLayout,
|
|
tileSize: _this.collectionTileSize,
|
|
tileMargin: _this.collectionTileMargin
|
|
});
|
|
_this.world.setAutoRefigureSizes(true);
|
|
}
|
|
}
|
|
|
|
if ($.isArray(options.tileSource)) {
|
|
setTimeout(function() {
|
|
raiseAddItemFailed({
|
|
message: "[Viewer.addTiledImage] Sequences can not be added; add them one at a time instead.",
|
|
source: options.tileSource,
|
|
options: options
|
|
});
|
|
});
|
|
return;
|
|
}
|
|
|
|
this._loadQueue.push(myQueueItem);
|
|
|
|
function processReadyItems() {
|
|
var queueItem, tiledImage, optionsClone;
|
|
while (_this._loadQueue.length) {
|
|
queueItem = _this._loadQueue[0];
|
|
if (!queueItem.tileSource) {
|
|
break;
|
|
}
|
|
|
|
_this._loadQueue.splice(0, 1);
|
|
|
|
if (queueItem.options.replace) {
|
|
var newIndex = _this.world.getIndexOfItem(queueItem.options.replaceItem);
|
|
if (newIndex !== -1) {
|
|
queueItem.options.index = newIndex;
|
|
}
|
|
_this.world.removeItem(queueItem.options.replaceItem);
|
|
}
|
|
|
|
tiledImage = new $.TiledImage({
|
|
viewer: _this,
|
|
source: queueItem.tileSource,
|
|
viewport: _this.viewport,
|
|
drawer: _this.drawer,
|
|
tileCache: _this.tileCache,
|
|
imageLoader: _this.imageLoader,
|
|
x: queueItem.options.x,
|
|
y: queueItem.options.y,
|
|
width: queueItem.options.width,
|
|
height: queueItem.options.height,
|
|
fitBounds: queueItem.options.fitBounds,
|
|
fitBoundsPlacement: queueItem.options.fitBoundsPlacement,
|
|
clip: queueItem.options.clip,
|
|
placeholderFillStyle: queueItem.options.placeholderFillStyle,
|
|
opacity: queueItem.options.opacity,
|
|
preload: queueItem.options.preload,
|
|
degrees: queueItem.options.degrees,
|
|
flipped: queueItem.options.flipped,
|
|
compositeOperation: queueItem.options.compositeOperation,
|
|
springStiffness: _this.springStiffness,
|
|
animationTime: _this.animationTime,
|
|
minZoomImageRatio: _this.minZoomImageRatio,
|
|
wrapHorizontal: _this.wrapHorizontal,
|
|
wrapVertical: _this.wrapVertical,
|
|
immediateRender: _this.immediateRender,
|
|
blendTime: _this.blendTime,
|
|
alwaysBlend: _this.alwaysBlend,
|
|
minPixelRatio: _this.minPixelRatio,
|
|
smoothTileEdgesMinZoom: _this.smoothTileEdgesMinZoom,
|
|
iOSDevice: _this.iOSDevice,
|
|
crossOriginPolicy: queueItem.options.crossOriginPolicy,
|
|
ajaxWithCredentials: queueItem.options.ajaxWithCredentials,
|
|
loadTilesWithAjax: queueItem.options.loadTilesWithAjax,
|
|
ajaxHeaders: queueItem.options.ajaxHeaders,
|
|
debugMode: _this.debugMode,
|
|
subPixelRoundingForTransparency: _this.subPixelRoundingForTransparency
|
|
});
|
|
|
|
if (_this.collectionMode) {
|
|
_this.world.setAutoRefigureSizes(false);
|
|
}
|
|
|
|
if (_this.navigator) {
|
|
optionsClone = $.extend({}, queueItem.options, {
|
|
replace: false, // navigator already removed the layer, nothing to replace
|
|
originalTiledImage: tiledImage,
|
|
tileSource: queueItem.tileSource
|
|
});
|
|
|
|
_this.navigator.addTiledImage(optionsClone);
|
|
}
|
|
|
|
_this.world.addItem( tiledImage, {
|
|
index: queueItem.options.index
|
|
});
|
|
|
|
if (_this._loadQueue.length === 0) {
|
|
//this restores the autoRefigureSizes flag to true.
|
|
refreshWorld(queueItem);
|
|
}
|
|
|
|
if (_this.world.getItemCount() === 1 && !_this.preserveViewport) {
|
|
_this.viewport.goHome(true);
|
|
}
|
|
|
|
if (queueItem.options.success) {
|
|
queueItem.options.success({
|
|
item: tiledImage
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
getTileSourceImplementation( this, options.tileSource, options, function( tileSource ) {
|
|
|
|
myQueueItem.tileSource = tileSource;
|
|
|
|
// add everybody at the front of the queue that's ready to go
|
|
processReadyItems();
|
|
}, function( event ) {
|
|
event.options = options;
|
|
raiseAddItemFailed(event);
|
|
|
|
// add everybody at the front of the queue that's ready to go
|
|
processReadyItems();
|
|
} );
|
|
},
|
|
|
|
/**
|
|
* Add a simple image to the viewer.
|
|
* The options are the same as the ones in {@link OpenSeadragon.Viewer#addTiledImage}
|
|
* except for options.tileSource which is replaced by options.url.
|
|
* @function
|
|
* @param {Object} options - See {@link OpenSeadragon.Viewer#addTiledImage}
|
|
* for all the options
|
|
* @param {String} options.url - The URL of the image to add.
|
|
* @fires OpenSeadragon.World.event:add-item
|
|
* @fires OpenSeadragon.Viewer.event:add-item-failed
|
|
*/
|
|
addSimpleImage: function(options) {
|
|
$.console.assert(options, "[Viewer.addSimpleImage] options is required");
|
|
$.console.assert(options.url, "[Viewer.addSimpleImage] options.url is required");
|
|
|
|
var opts = $.extend({}, options, {
|
|
tileSource: {
|
|
type: 'image',
|
|
url: options.url
|
|
}
|
|
});
|
|
delete opts.url;
|
|
this.addTiledImage(opts);
|
|
},
|
|
|
|
// deprecated
|
|
addLayer: function( options ) {
|
|
var _this = this;
|
|
|
|
$.console.error( "[Viewer.addLayer] this function is deprecated; use Viewer.addTiledImage() instead." );
|
|
|
|
var optionsClone = $.extend({}, options, {
|
|
success: function(event) {
|
|
_this.raiseEvent("add-layer", {
|
|
options: options,
|
|
drawer: event.item
|
|
});
|
|
},
|
|
error: function(event) {
|
|
_this.raiseEvent("add-layer-failed", event);
|
|
}
|
|
});
|
|
|
|
this.addTiledImage(optionsClone);
|
|
return this;
|
|
},
|
|
|
|
// deprecated
|
|
getLayerAtLevel: function( level ) {
|
|
$.console.error( "[Viewer.getLayerAtLevel] this function is deprecated; use World.getItemAt() instead." );
|
|
return this.world.getItemAt(level);
|
|
},
|
|
|
|
// deprecated
|
|
getLevelOfLayer: function( drawer ) {
|
|
$.console.error( "[Viewer.getLevelOfLayer] this function is deprecated; use World.getIndexOfItem() instead." );
|
|
return this.world.getIndexOfItem(drawer);
|
|
},
|
|
|
|
// deprecated
|
|
getLayersCount: function() {
|
|
$.console.error( "[Viewer.getLayersCount] this function is deprecated; use World.getItemCount() instead." );
|
|
return this.world.getItemCount();
|
|
},
|
|
|
|
// deprecated
|
|
setLayerLevel: function( drawer, level ) {
|
|
$.console.error( "[Viewer.setLayerLevel] this function is deprecated; use World.setItemIndex() instead." );
|
|
return this.world.setItemIndex(drawer, level);
|
|
},
|
|
|
|
// deprecated
|
|
removeLayer: function( drawer ) {
|
|
$.console.error( "[Viewer.removeLayer] this function is deprecated; use World.removeItem() instead." );
|
|
return this.world.removeItem(drawer);
|
|
},
|
|
|
|
/**
|
|
* Force the viewer to redraw its contents.
|
|
* @returns {OpenSeadragon.Viewer} Chainable.
|
|
*/
|
|
forceRedraw: function() {
|
|
THIS[ this.hash ].forceRedraw = true;
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* @function
|
|
* @return {OpenSeadragon.Viewer} Chainable.
|
|
*/
|
|
bindSequenceControls: function(){
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Image Sequence Controls
|
|
//////////////////////////////////////////////////////////////////////////
|
|
var onFocusHandler = $.delegate( this, onFocus ),
|
|
onBlurHandler = $.delegate( this, onBlur ),
|
|
onNextHandler = $.delegate( this, this.goToNextPage ),
|
|
onPreviousHandler = $.delegate( this, this.goToPreviousPage ),
|
|
navImages = this.navImages,
|
|
useGroup = true;
|
|
|
|
if( this.showSequenceControl ){
|
|
|
|
if( this.previousButton || this.nextButton ){
|
|
//if we are binding to custom buttons then layout and
|
|
//grouping is the responsibility of the page author
|
|
useGroup = false;
|
|
}
|
|
|
|
this.previousButton = new $.Button({
|
|
element: this.previousButton ? $.getElement( this.previousButton ) : null,
|
|
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
|
|
});
|
|
|
|
this.nextButton = new $.Button({
|
|
element: this.nextButton ? $.getElement( this.nextButton ) : null,
|
|
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
|
|
});
|
|
|
|
if( !this.navPrevNextWrap ){
|
|
this.previousButton.disable();
|
|
}
|
|
|
|
if (!this.tileSources || !this.tileSources.length) {
|
|
this.nextButton.disable();
|
|
}
|
|
|
|
if( useGroup ){
|
|
this.paging = new $.ButtonGroup({
|
|
buttons: [
|
|
this.previousButton,
|
|
this.nextButton
|
|
],
|
|
clickTimeThreshold: this.clickTimeThreshold,
|
|
clickDistThreshold: this.clickDistThreshold
|
|
});
|
|
|
|
this.pagingControl = this.paging.element;
|
|
|
|
if( this.toolbar ){
|
|
this.toolbar.addControl(
|
|
this.pagingControl,
|
|
{anchor: $.ControlAnchor.BOTTOM_RIGHT}
|
|
);
|
|
}else{
|
|
this.addControl(
|
|
this.pagingControl,
|
|
{anchor: this.sequenceControlAnchor || $.ControlAnchor.TOP_LEFT}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
return this;
|
|
},
|
|
|
|
|
|
/**
|
|
* @function
|
|
* @return {OpenSeadragon.Viewer} Chainable.
|
|
*/
|
|
bindStandardControls: function(){
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Navigation Controls
|
|
//////////////////////////////////////////////////////////////////////////
|
|
var beginZoomingInHandler = $.delegate( this, beginZoomingIn ),
|
|
endZoomingHandler = $.delegate( this, endZooming ),
|
|
doSingleZoomInHandler = $.delegate( this, doSingleZoomIn ),
|
|
beginZoomingOutHandler = $.delegate( this, beginZoomingOut ),
|
|
doSingleZoomOutHandler = $.delegate( this, doSingleZoomOut ),
|
|
onHomeHandler = $.delegate( this, onHome ),
|
|
onFullScreenHandler = $.delegate( this, onFullScreen ),
|
|
onRotateLeftHandler = $.delegate( this, onRotateLeft ),
|
|
onRotateRightHandler = $.delegate( this, onRotateRight ),
|
|
onFlipHandler = $.delegate( this, onFlip),
|
|
onFocusHandler = $.delegate( this, onFocus ),
|
|
onBlurHandler = $.delegate( this, onBlur ),
|
|
navImages = this.navImages,
|
|
buttons = [],
|
|
useGroup = true;
|
|
|
|
|
|
if ( this.showNavigationControl ) {
|
|
|
|
if( this.zoomInButton || this.zoomOutButton ||
|
|
this.homeButton || this.fullPageButton ||
|
|
this.rotateLeftButton || this.rotateRightButton ||
|
|
this.flipButton ) {
|
|
//if we are binding to custom buttons then layout and
|
|
//grouping is the responsibility of the page author
|
|
useGroup = false;
|
|
}
|
|
|
|
if ( this.showZoomControl ) {
|
|
buttons.push( this.zoomInButton = new $.Button({
|
|
element: this.zoomInButton ? $.getElement( this.zoomInButton ) : null,
|
|
clickTimeThreshold: this.clickTimeThreshold,
|
|
clickDistThreshold: this.clickDistThreshold,
|
|
tooltip: $.getString( "Tooltips.ZoomIn" ),
|
|
srcRest: resolveUrl( this.prefixUrl, navImages.zoomIn.REST ),
|
|
srcGroup: resolveUrl( this.prefixUrl, navImages.zoomIn.GROUP ),
|
|
srcHover: resolveUrl( this.prefixUrl, navImages.zoomIn.HOVER ),
|
|
srcDown: resolveUrl( this.prefixUrl, navImages.zoomIn.DOWN ),
|
|
onPress: beginZoomingInHandler,
|
|
onRelease: endZoomingHandler,
|
|
onClick: doSingleZoomInHandler,
|
|
onEnter: beginZoomingInHandler,
|
|
onExit: endZoomingHandler,
|
|
onFocus: onFocusHandler,
|
|
onBlur: onBlurHandler
|
|
}));
|
|
|
|
buttons.push( this.zoomOutButton = new $.Button({
|
|
element: this.zoomOutButton ? $.getElement( this.zoomOutButton ) : null,
|
|
clickTimeThreshold: this.clickTimeThreshold,
|
|
clickDistThreshold: this.clickDistThreshold,
|
|
tooltip: $.getString( "Tooltips.ZoomOut" ),
|
|
srcRest: resolveUrl( this.prefixUrl, navImages.zoomOut.REST ),
|
|
srcGroup: resolveUrl( this.prefixUrl, navImages.zoomOut.GROUP ),
|
|
srcHover: resolveUrl( this.prefixUrl, navImages.zoomOut.HOVER ),
|
|
srcDown: resolveUrl( this.prefixUrl, navImages.zoomOut.DOWN ),
|
|
onPress: beginZoomingOutHandler,
|
|
onRelease: endZoomingHandler,
|
|
onClick: doSingleZoomOutHandler,
|
|
onEnter: beginZoomingOutHandler,
|
|
onExit: endZoomingHandler,
|
|
onFocus: onFocusHandler,
|
|
onBlur: onBlurHandler
|
|
}));
|
|
}
|
|
|
|
if ( this.showHomeControl ) {
|
|
buttons.push( this.homeButton = new $.Button({
|
|
element: this.homeButton ? $.getElement( this.homeButton ) : null,
|
|
clickTimeThreshold: this.clickTimeThreshold,
|
|
clickDistThreshold: this.clickDistThreshold,
|
|
tooltip: $.getString( "Tooltips.Home" ),
|
|
srcRest: resolveUrl( this.prefixUrl, navImages.home.REST ),
|
|
srcGroup: resolveUrl( this.prefixUrl, navImages.home.GROUP ),
|
|
srcHover: resolveUrl( this.prefixUrl, navImages.home.HOVER ),
|
|
srcDown: resolveUrl( this.prefixUrl, navImages.home.DOWN ),
|
|
onRelease: onHomeHandler,
|
|
onFocus: onFocusHandler,
|
|
onBlur: onBlurHandler
|
|
}));
|
|
}
|
|
|
|
if ( this.showFullPageControl ) {
|
|
buttons.push( this.fullPageButton = new $.Button({
|
|
element: this.fullPageButton ? $.getElement( this.fullPageButton ) : null,
|
|
clickTimeThreshold: this.clickTimeThreshold,
|
|
clickDistThreshold: this.clickDistThreshold,
|
|
tooltip: $.getString( "Tooltips.FullPage" ),
|
|
srcRest: resolveUrl( this.prefixUrl, navImages.fullpage.REST ),
|
|
srcGroup: resolveUrl( this.prefixUrl, navImages.fullpage.GROUP ),
|
|
srcHover: resolveUrl( this.prefixUrl, navImages.fullpage.HOVER ),
|
|
srcDown: resolveUrl( this.prefixUrl, navImages.fullpage.DOWN ),
|
|
onRelease: onFullScreenHandler,
|
|
onFocus: onFocusHandler,
|
|
onBlur: onBlurHandler
|
|
}));
|
|
}
|
|
|
|
if ( this.showRotationControl ) {
|
|
buttons.push( this.rotateLeftButton = new $.Button({
|
|
element: this.rotateLeftButton ? $.getElement( this.rotateLeftButton ) : null,
|
|
clickTimeThreshold: this.clickTimeThreshold,
|
|
clickDistThreshold: this.clickDistThreshold,
|
|
tooltip: $.getString( "Tooltips.RotateLeft" ),
|
|
srcRest: resolveUrl( this.prefixUrl, navImages.rotateleft.REST ),
|
|
srcGroup: resolveUrl( this.prefixUrl, navImages.rotateleft.GROUP ),
|
|
srcHover: resolveUrl( this.prefixUrl, navImages.rotateleft.HOVER ),
|
|
srcDown: resolveUrl( this.prefixUrl, navImages.rotateleft.DOWN ),
|
|
onRelease: onRotateLeftHandler,
|
|
onFocus: onFocusHandler,
|
|
onBlur: onBlurHandler
|
|
}));
|
|
|
|
buttons.push( this.rotateRightButton = new $.Button({
|
|
element: this.rotateRightButton ? $.getElement( this.rotateRightButton ) : null,
|
|
clickTimeThreshold: this.clickTimeThreshold,
|
|
clickDistThreshold: this.clickDistThreshold,
|
|
tooltip: $.getString( "Tooltips.RotateRight" ),
|
|
srcRest: resolveUrl( this.prefixUrl, navImages.rotateright.REST ),
|
|
srcGroup: resolveUrl( this.prefixUrl, navImages.rotateright.GROUP ),
|
|
srcHover: resolveUrl( this.prefixUrl, navImages.rotateright.HOVER ),
|
|
srcDown: resolveUrl( this.prefixUrl, navImages.rotateright.DOWN ),
|
|
onRelease: onRotateRightHandler,
|
|
onFocus: onFocusHandler,
|
|
onBlur: onBlurHandler
|
|
}));
|
|
}
|
|
|
|
if ( this.showFlipControl ) {
|
|
buttons.push( this.flipButton = new $.Button({
|
|
element: this.flipButton ? $.getElement( this.flipButton ) : null,
|
|
clickTimeThreshold: this.clickTimeThreshold,
|
|
clickDistThreshold: this.clickDistThreshold,
|
|
tooltip: $.getString( "Tooltips.Flip" ),
|
|
srcRest: resolveUrl( this.prefixUrl, navImages.flip.REST ),
|
|
srcGroup: resolveUrl( this.prefixUrl, navImages.flip.GROUP ),
|
|
srcHover: resolveUrl( this.prefixUrl, navImages.flip.HOVER ),
|
|
srcDown: resolveUrl( this.prefixUrl, navImages.flip.DOWN ),
|
|
onRelease: onFlipHandler,
|
|
onFocus: onFocusHandler,
|
|
onBlur: onBlurHandler
|
|
}));
|
|
}
|
|
|
|
if ( useGroup ) {
|
|
this.buttonGroup = new $.ButtonGroup({
|
|
buttons: buttons,
|
|
clickTimeThreshold: this.clickTimeThreshold,
|
|
clickDistThreshold: this.clickDistThreshold
|
|
});
|
|
|
|
this.navControl = this.buttonGroup.element;
|
|
this.addHandler( 'open', $.delegate( this, lightUp ) );
|
|
|
|
if( this.toolbar ){
|
|
this.toolbar.addControl(
|
|
this.navControl,
|
|
{anchor: this.navigationControlAnchor || $.ControlAnchor.TOP_LEFT}
|
|
);
|
|
} else {
|
|
this.addControl(
|
|
this.navControl,
|
|
{anchor: this.navigationControlAnchor || $.ControlAnchor.TOP_LEFT}
|
|
);
|
|
}
|
|
} else {
|
|
this.customButtons = buttons;
|
|
}
|
|
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Gets the active page of a sequence
|
|
* @function
|
|
* @return {Number}
|
|
*/
|
|
currentPage: function() {
|
|
return this._sequenceIndex;
|
|
},
|
|
|
|
/**
|
|
* @function
|
|
* @return {OpenSeadragon.Viewer} Chainable.
|
|
* @fires OpenSeadragon.Viewer.event:page
|
|
*/
|
|
goToPage: function( page ){
|
|
if( this.tileSources && page >= 0 && page < this.tileSources.length ){
|
|
this._sequenceIndex = page;
|
|
|
|
this._updateSequenceButtons( page );
|
|
|
|
this.open( this.tileSources[ page ] );
|
|
|
|
if( this.referenceStrip ){
|
|
this.referenceStrip.setFocus( page );
|
|
}
|
|
|
|
/**
|
|
* Raised when the page is changed on a viewer configured with multiple image sources (see {@link OpenSeadragon.Viewer#goToPage}).
|
|
*
|
|
* @event page
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {Object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
|
* @property {Number} page - The page index.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'page', { page: page } );
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Adds an html element as an overlay to the current viewport. Useful for
|
|
* highlighting words or areas of interest on an image or other zoomable
|
|
* interface. The overlays added via this method are removed when the viewport
|
|
* is closed which include when changing page.
|
|
* @method
|
|
* @param {Element|String|Object} element - A reference to an element or an id for
|
|
* the element which will be overlaid. Or an Object specifying the configuration for the overlay.
|
|
* If using an object, see {@link OpenSeadragon.Overlay} for a list of
|
|
* all available options.
|
|
* @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
|
|
* rectangle which will be overlaid. This is a viewport relative location.
|
|
* @param {OpenSeadragon.Placement} [placement=OpenSeadragon.Placement.TOP_LEFT] - The position of the
|
|
* viewport which the location coordinates will be treated as relative
|
|
* to.
|
|
* @param {function} [onDraw] - If supplied the callback is called when the overlay
|
|
* needs to be drawn. It it the responsibility of the callback to do any drawing/positioning.
|
|
* It is passed position, size and element.
|
|
* @return {OpenSeadragon.Viewer} Chainable.
|
|
* @fires OpenSeadragon.Viewer.event:add-overlay
|
|
*/
|
|
addOverlay: function( element, location, placement, onDraw ) {
|
|
var options;
|
|
if( $.isPlainObject( element ) ){
|
|
options = element;
|
|
} else {
|
|
options = {
|
|
element: element,
|
|
location: location,
|
|
placement: placement,
|
|
onDraw: onDraw
|
|
};
|
|
}
|
|
|
|
element = $.getElement( options.element );
|
|
|
|
if ( getOverlayIndex( this.currentOverlays, element ) >= 0 ) {
|
|
// they're trying to add a duplicate overlay
|
|
return this;
|
|
}
|
|
|
|
var overlay = getOverlayObject( this, options);
|
|
this.currentOverlays.push(overlay);
|
|
overlay.drawHTML( this.overlaysContainer, this.viewport );
|
|
|
|
/**
|
|
* Raised when an overlay is added to the viewer (see {@link OpenSeadragon.Viewer#addOverlay}).
|
|
*
|
|
* @event add-overlay
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
|
* @property {Element} element - The overlay element.
|
|
* @property {OpenSeadragon.Point|OpenSeadragon.Rect} location
|
|
* @property {OpenSeadragon.Placement} placement
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'add-overlay', {
|
|
element: element,
|
|
location: options.location,
|
|
placement: options.placement
|
|
});
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Updates the overlay represented by the reference to the element or
|
|
* element id moving it to the new location, relative to the new placement.
|
|
* @method
|
|
* @param {Element|String} element - A reference to an element or an id for
|
|
* the element which is overlaid.
|
|
* @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
|
|
* rectangle which will be overlaid. This is a viewport relative location.
|
|
* @param {OpenSeadragon.Placement} [placement=OpenSeadragon.Placement.TOP_LEFT] - The position of the
|
|
* viewport which the location coordinates will be treated as relative
|
|
* to.
|
|
* @return {OpenSeadragon.Viewer} Chainable.
|
|
* @fires OpenSeadragon.Viewer.event:update-overlay
|
|
*/
|
|
updateOverlay: function( element, location, placement ) {
|
|
var i;
|
|
|
|
element = $.getElement( element );
|
|
i = getOverlayIndex( this.currentOverlays, element );
|
|
|
|
if ( i >= 0 ) {
|
|
this.currentOverlays[ i ].update( location, placement );
|
|
THIS[ this.hash ].forceRedraw = true;
|
|
/**
|
|
* Raised when an overlay's location or placement changes
|
|
* (see {@link OpenSeadragon.Viewer#updateOverlay}).
|
|
*
|
|
* @event update-overlay
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the
|
|
* Viewer which raised the event.
|
|
* @property {Element} element
|
|
* @property {OpenSeadragon.Point|OpenSeadragon.Rect} location
|
|
* @property {OpenSeadragon.Placement} placement
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'update-overlay', {
|
|
element: element,
|
|
location: location,
|
|
placement: placement
|
|
});
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Removes an overlay identified by the reference element or element id
|
|
* and schedules an update.
|
|
* @method
|
|
* @param {Element|String} element - A reference to the element or an
|
|
* element id which represent the ovelay content to be removed.
|
|
* @return {OpenSeadragon.Viewer} Chainable.
|
|
* @fires OpenSeadragon.Viewer.event:remove-overlay
|
|
*/
|
|
removeOverlay: function( element ) {
|
|
var i;
|
|
|
|
element = $.getElement( element );
|
|
i = getOverlayIndex( this.currentOverlays, element );
|
|
|
|
if ( i >= 0 ) {
|
|
this.currentOverlays[ i ].destroy();
|
|
this.currentOverlays.splice( i, 1 );
|
|
THIS[ this.hash ].forceRedraw = true;
|
|
/**
|
|
* Raised when an overlay is removed from the viewer
|
|
* (see {@link OpenSeadragon.Viewer#removeOverlay}).
|
|
*
|
|
* @event remove-overlay
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the
|
|
* Viewer which raised the event.
|
|
* @property {Element} element - The overlay element.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'remove-overlay', {
|
|
element: element
|
|
});
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Removes all currently configured Overlays from this Viewer and schedules
|
|
* an update.
|
|
* @method
|
|
* @return {OpenSeadragon.Viewer} Chainable.
|
|
* @fires OpenSeadragon.Viewer.event:clear-overlay
|
|
*/
|
|
clearOverlays: function() {
|
|
while ( this.currentOverlays.length > 0 ) {
|
|
this.currentOverlays.pop().destroy();
|
|
}
|
|
THIS[ this.hash ].forceRedraw = true;
|
|
/**
|
|
* Raised when all overlays are removed from the viewer (see {@link OpenSeadragon.Drawer#clearOverlays}).
|
|
*
|
|
* @event clear-overlay
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'clear-overlay', {} );
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Finds an overlay identified by the reference element or element id
|
|
* and returns it as an object, return null if not found.
|
|
* @method
|
|
* @param {Element|String} element - A reference to the element or an
|
|
* element id which represents the overlay content.
|
|
* @return {OpenSeadragon.Overlay} the matching overlay or null if none found.
|
|
*/
|
|
getOverlayById: function( element ) {
|
|
var i;
|
|
|
|
element = $.getElement( element );
|
|
i = getOverlayIndex( this.currentOverlays, element );
|
|
|
|
if (i >= 0) {
|
|
return this.currentOverlays[i];
|
|
} else {
|
|
return null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Updates the sequence buttons.
|
|
* @function OpenSeadragon.Viewer.prototype._updateSequenceButtons
|
|
* @private
|
|
* @param {Number} Sequence Value
|
|
*/
|
|
_updateSequenceButtons: function( page ) {
|
|
|
|
if ( this.nextButton ) {
|
|
if(!this.tileSources || this.tileSources.length - 1 === page) {
|
|
//Disable next button
|
|
if ( !this.navPrevNextWrap ) {
|
|
this.nextButton.disable();
|
|
}
|
|
} else {
|
|
this.nextButton.enable();
|
|
}
|
|
}
|
|
if ( this.previousButton ) {
|
|
if ( page > 0 ) {
|
|
//Enable previous button
|
|
this.previousButton.enable();
|
|
} else {
|
|
if ( !this.navPrevNextWrap ) {
|
|
this.previousButton.disable();
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Display a message in the viewport
|
|
* @function OpenSeadragon.Viewer.prototype._showMessage
|
|
* @private
|
|
* @param {String} text message
|
|
*/
|
|
_showMessage: function ( message ) {
|
|
this._hideMessage();
|
|
|
|
var div = $.makeNeutralElement( "div" );
|
|
div.appendChild( document.createTextNode( message ) );
|
|
|
|
this.messageDiv = $.makeCenteredNode( div );
|
|
|
|
$.addClass(this.messageDiv, "openseadragon-message");
|
|
|
|
this.container.appendChild( this.messageDiv );
|
|
},
|
|
|
|
/**
|
|
* Hide any currently displayed viewport message
|
|
* @function OpenSeadragon.Viewer.prototype._hideMessage
|
|
* @private
|
|
*/
|
|
_hideMessage: function () {
|
|
var div = this.messageDiv;
|
|
if (div) {
|
|
div.parentNode.removeChild(div);
|
|
delete this.messageDiv;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Gets this viewer's gesture settings for the given pointer device type.
|
|
* @method
|
|
* @param {String} type - The pointer device type to get the gesture settings for ("mouse", "touch", "pen", etc.).
|
|
* @return {OpenSeadragon.GestureSettings}
|
|
*/
|
|
gestureSettingsByDeviceType: function ( type ) {
|
|
switch ( type ) {
|
|
case 'mouse':
|
|
return this.gestureSettingsMouse;
|
|
case 'touch':
|
|
return this.gestureSettingsTouch;
|
|
case 'pen':
|
|
return this.gestureSettingsPen;
|
|
default:
|
|
return this.gestureSettingsUnknown;
|
|
}
|
|
},
|
|
|
|
// private
|
|
_drawOverlays: function() {
|
|
var i,
|
|
length = this.currentOverlays.length;
|
|
for ( i = 0; i < length; i++ ) {
|
|
this.currentOverlays[ i ].drawHTML( this.overlaysContainer, this.viewport );
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Cancel the "in flight" images.
|
|
*/
|
|
_cancelPendingImages: function() {
|
|
this._loadQueue = [];
|
|
},
|
|
|
|
/**
|
|
* Removes the reference strip and disables displaying it.
|
|
* @function
|
|
*/
|
|
removeReferenceStrip: function() {
|
|
this.showReferenceStrip = false;
|
|
|
|
if (this.referenceStrip) {
|
|
this.referenceStrip.destroy();
|
|
this.referenceStrip = null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Enables and displays the reference strip based on the currently set tileSources.
|
|
* Works only when the Viewer has sequenceMode set to true.
|
|
* @function
|
|
*/
|
|
addReferenceStrip: function() {
|
|
this.showReferenceStrip = true;
|
|
|
|
if (this.sequenceMode) {
|
|
if (this.referenceStrip) {
|
|
return;
|
|
}
|
|
|
|
if (this.tileSources.length && this.tileSources.length > 1) {
|
|
this.referenceStrip = new $.ReferenceStrip({
|
|
id: this.referenceStripElement,
|
|
position: this.referenceStripPosition,
|
|
sizeRatio: this.referenceStripSizeRatio,
|
|
scroll: this.referenceStripScroll,
|
|
height: this.referenceStripHeight,
|
|
width: this.referenceStripWidth,
|
|
tileSources: this.tileSources,
|
|
prefixUrl: this.prefixUrl,
|
|
useCanvas: this.useCanvas,
|
|
viewer: this
|
|
});
|
|
|
|
this.referenceStrip.setFocus( this._sequenceIndex );
|
|
}
|
|
} else {
|
|
$.console.warn('Attempting to display a reference strip while "sequenceMode" is off.');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Adds _updatePixelDensityRatio to the window resize event.
|
|
* @private
|
|
*/
|
|
_addUpdatePixelDensityRatioEvent: function() {
|
|
this._updatePixelDensityRatioBind = this._updatePixelDensityRatio.bind(this);
|
|
$.addEvent( window, 'resize', this._updatePixelDensityRatioBind );
|
|
},
|
|
|
|
/**
|
|
* Removes _updatePixelDensityRatio from the window resize event.
|
|
* @private
|
|
*/
|
|
_removeUpdatePixelDensityRatioEvent: function() {
|
|
$.removeEvent( window, 'resize', this._updatePixelDensityRatioBind );
|
|
},
|
|
|
|
/**
|
|
* Update pixel density ratio, clears all tiles and triggers updates for
|
|
* all items if the ratio has changed.
|
|
* @private
|
|
*/
|
|
_updatePixelDensityRatio: function() {
|
|
var previusPixelDensityRatio = $.pixelDensityRatio;
|
|
var currentPixelDensityRatio = $.getCurrentPixelDensityRatio();
|
|
if (previusPixelDensityRatio !== currentPixelDensityRatio) {
|
|
$.pixelDensityRatio = currentPixelDensityRatio;
|
|
this.world.resetItems();
|
|
this.forceRedraw();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Sets the image source to the source with index equal to
|
|
* currentIndex - 1. Changes current image in sequence mode.
|
|
* If specified, wraps around (see navPrevNextWrap in
|
|
* {@link OpenSeadragon.Options})
|
|
*
|
|
* @method
|
|
*/
|
|
|
|
goToPreviousPage: function () {
|
|
var previous = this._sequenceIndex - 1;
|
|
if(this.navPrevNextWrap && previous < 0){
|
|
previous += this.tileSources.length;
|
|
}
|
|
this.goToPage( previous );
|
|
},
|
|
|
|
/**
|
|
* Sets the image source to the source with index equal to
|
|
* currentIndex + 1. Changes current image in sequence mode.
|
|
* If specified, wraps around (see navPrevNextWrap in
|
|
* {@link OpenSeadragon.Options})
|
|
*
|
|
* @method
|
|
*/
|
|
goToNextPage: function () {
|
|
var next = this._sequenceIndex + 1;
|
|
if(this.navPrevNextWrap && next >= this.tileSources.length){
|
|
next = 0;
|
|
}
|
|
this.goToPage( next );
|
|
},
|
|
|
|
isAnimating: function () {
|
|
return THIS[ this.hash ].animating;
|
|
},
|
|
});
|
|
|
|
|
|
/**
|
|
* _getSafeElemSize is like getElementSize(), but refuses to return 0 for x or y,
|
|
* which was causing some calling operations to return NaN.
|
|
* @returns {Point}
|
|
* @private
|
|
*/
|
|
function _getSafeElemSize (oElement) {
|
|
oElement = $.getElement( oElement );
|
|
|
|
return new $.Point(
|
|
(oElement.clientWidth === 0 ? 1 : oElement.clientWidth),
|
|
(oElement.clientHeight === 0 ? 1 : oElement.clientHeight)
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* @function
|
|
* @private
|
|
*/
|
|
function getTileSourceImplementation( viewer, tileSource, imgOptions, successCallback,
|
|
failCallback ) {
|
|
var _this = viewer;
|
|
|
|
//allow plain xml strings or json strings to be parsed here
|
|
if ( $.type( tileSource ) === 'string' ) {
|
|
//xml should start with "<" and end with ">"
|
|
if ( tileSource.match( /^\s*<.*>\s*$/ ) ) {
|
|
tileSource = $.parseXml( tileSource );
|
|
//json should start with "{" or "[" and end with "}" or "]"
|
|
} else if ( tileSource.match(/^\s*[{[].*[}\]]\s*$/ ) ) {
|
|
try {
|
|
var tileSourceJ = $.parseJSON(tileSource);
|
|
tileSource = tileSourceJ;
|
|
} catch (e) {
|
|
//tileSource = tileSource;
|
|
}
|
|
}
|
|
}
|
|
|
|
function waitUntilReady(tileSource, originalTileSource) {
|
|
if (tileSource.ready) {
|
|
successCallback(tileSource);
|
|
} else {
|
|
tileSource.addHandler('ready', function () {
|
|
successCallback(tileSource);
|
|
});
|
|
tileSource.addHandler('open-failed', function (event) {
|
|
failCallback({
|
|
message: event.message,
|
|
source: originalTileSource
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
setTimeout( function() {
|
|
if ( $.type( tileSource ) === 'string' ) {
|
|
//If its still a string it means it must be a url at this point
|
|
tileSource = new $.TileSource({
|
|
url: tileSource,
|
|
crossOriginPolicy: imgOptions.crossOriginPolicy !== undefined ?
|
|
imgOptions.crossOriginPolicy : viewer.crossOriginPolicy,
|
|
ajaxWithCredentials: viewer.ajaxWithCredentials,
|
|
ajaxHeaders: imgOptions.ajaxHeaders ?
|
|
imgOptions.ajaxHeaders : viewer.ajaxHeaders,
|
|
splitHashDataForPost: viewer.splitHashDataForPost,
|
|
useCanvas: viewer.useCanvas,
|
|
success: function( event ) {
|
|
successCallback( event.tileSource );
|
|
}
|
|
});
|
|
tileSource.addHandler( 'open-failed', function( event ) {
|
|
failCallback( event );
|
|
} );
|
|
|
|
} else if ($.isPlainObject(tileSource) || tileSource.nodeType) {
|
|
if (tileSource.crossOriginPolicy === undefined &&
|
|
(imgOptions.crossOriginPolicy !== undefined || viewer.crossOriginPolicy !== undefined)) {
|
|
tileSource.crossOriginPolicy = imgOptions.crossOriginPolicy !== undefined ?
|
|
imgOptions.crossOriginPolicy : viewer.crossOriginPolicy;
|
|
}
|
|
if (tileSource.ajaxWithCredentials === undefined) {
|
|
tileSource.ajaxWithCredentials = viewer.ajaxWithCredentials;
|
|
}
|
|
if (tileSource.useCanvas === undefined) {
|
|
tileSource.useCanvas = viewer.useCanvas;
|
|
}
|
|
|
|
if ( $.isFunction( tileSource.getTileUrl ) ) {
|
|
//Custom tile source
|
|
var customTileSource = new $.TileSource( tileSource );
|
|
customTileSource.getTileUrl = tileSource.getTileUrl;
|
|
successCallback( customTileSource );
|
|
} else {
|
|
//inline configuration
|
|
var $TileSource = $.TileSource.determineType( _this, tileSource );
|
|
if ( !$TileSource ) {
|
|
failCallback( {
|
|
message: "Unable to load TileSource",
|
|
source: tileSource
|
|
});
|
|
return;
|
|
}
|
|
var options = $TileSource.prototype.configure.apply( _this, [ tileSource ] );
|
|
waitUntilReady(new $TileSource(options), tileSource);
|
|
}
|
|
} else {
|
|
//can assume it's already a tile source implementation
|
|
waitUntilReady(tileSource, tileSource);
|
|
}
|
|
});
|
|
}
|
|
|
|
function getOverlayObject( viewer, overlay ) {
|
|
if ( overlay instanceof $.Overlay ) {
|
|
return overlay;
|
|
}
|
|
|
|
var element = null;
|
|
if ( overlay.element ) {
|
|
element = $.getElement( overlay.element );
|
|
} else {
|
|
var id = overlay.id ?
|
|
overlay.id :
|
|
"openseadragon-overlay-" + Math.floor( Math.random() * 10000000 );
|
|
|
|
element = $.getElement( overlay.id );
|
|
if ( !element ) {
|
|
element = document.createElement( "a" );
|
|
element.href = "#/overlay/" + id;
|
|
}
|
|
element.id = id;
|
|
$.addClass( element, overlay.className ?
|
|
overlay.className :
|
|
"openseadragon-overlay"
|
|
);
|
|
}
|
|
|
|
var location = overlay.location;
|
|
var width = overlay.width;
|
|
var height = overlay.height;
|
|
if (!location) {
|
|
var x = overlay.x;
|
|
var y = overlay.y;
|
|
if (overlay.px !== undefined) {
|
|
var rect = viewer.viewport.imageToViewportRectangle(new $.Rect(
|
|
overlay.px,
|
|
overlay.py,
|
|
width || 0,
|
|
height || 0));
|
|
x = rect.x;
|
|
y = rect.y;
|
|
width = width !== undefined ? rect.width : undefined;
|
|
height = height !== undefined ? rect.height : undefined;
|
|
}
|
|
location = new $.Point(x, y);
|
|
}
|
|
|
|
var placement = overlay.placement;
|
|
if (placement && $.type(placement) === "string") {
|
|
placement = $.Placement[overlay.placement.toUpperCase()];
|
|
}
|
|
|
|
return new $.Overlay({
|
|
element: element,
|
|
location: location,
|
|
placement: placement,
|
|
onDraw: overlay.onDraw,
|
|
checkResize: overlay.checkResize,
|
|
width: width,
|
|
height: height,
|
|
rotationMode: overlay.rotationMode
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* @inner
|
|
* Determines the index of the given overlay in the given overlays array.
|
|
*/
|
|
function getOverlayIndex( overlays, element ) {
|
|
var i;
|
|
for ( i = overlays.length - 1; i >= 0; i-- ) {
|
|
if ( overlays[ i ].element === element ) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Schedulers provide the general engine for animation
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
function scheduleUpdate( viewer, updateFunc ){
|
|
return $.requestAnimationFrame( function(){
|
|
updateFunc( viewer );
|
|
} );
|
|
}
|
|
|
|
|
|
//provides a sequence in the fade animation
|
|
function scheduleControlsFade( viewer ) {
|
|
$.requestAnimationFrame( function(){
|
|
updateControlsFade( viewer );
|
|
});
|
|
}
|
|
|
|
|
|
//initiates an animation to hide the controls
|
|
function beginControlsAutoHide( viewer ) {
|
|
if ( !viewer.autoHideControls ) {
|
|
return;
|
|
}
|
|
viewer.controlsShouldFade = true;
|
|
viewer.controlsFadeBeginTime =
|
|
$.now() +
|
|
viewer.controlsFadeDelay;
|
|
|
|
window.setTimeout( function(){
|
|
scheduleControlsFade( viewer );
|
|
}, viewer.controlsFadeDelay );
|
|
}
|
|
|
|
|
|
//determines if fade animation is done or continues the animation
|
|
function updateControlsFade( viewer ) {
|
|
var currentTime,
|
|
deltaTime,
|
|
opacity,
|
|
i;
|
|
if ( viewer.controlsShouldFade ) {
|
|
currentTime = $.now();
|
|
deltaTime = currentTime - viewer.controlsFadeBeginTime;
|
|
opacity = 1.0 - deltaTime / viewer.controlsFadeLength;
|
|
|
|
opacity = Math.min( 1.0, opacity );
|
|
opacity = Math.max( 0.0, opacity );
|
|
|
|
for ( i = viewer.controls.length - 1; i >= 0; i--) {
|
|
if (viewer.controls[ i ].autoFade) {
|
|
viewer.controls[ i ].setOpacity( opacity );
|
|
}
|
|
}
|
|
|
|
if ( opacity > 0 ) {
|
|
// fade again
|
|
scheduleControlsFade( viewer );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//stop the fade animation on the controls and show them
|
|
function abortControlsAutoHide( viewer ) {
|
|
var i;
|
|
viewer.controlsShouldFade = false;
|
|
for ( i = viewer.controls.length - 1; i >= 0; i-- ) {
|
|
viewer.controls[ i ].setOpacity( 1.0 );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Default view event handlers.
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
function onFocus(){
|
|
abortControlsAutoHide( this );
|
|
}
|
|
|
|
function onBlur(){
|
|
beginControlsAutoHide( this );
|
|
|
|
}
|
|
|
|
function onCanvasContextMenu( event ) {
|
|
var eventArgs = {
|
|
tracker: event.eventSource,
|
|
position: event.position,
|
|
originalEvent: event.originalEvent,
|
|
preventDefault: event.preventDefault
|
|
};
|
|
|
|
/**
|
|
* Raised when a contextmenu event occurs in the {@link OpenSeadragon.Viewer#canvas} element.
|
|
*
|
|
* @event canvas-contextmenu
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
|
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
|
|
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
|
|
* @property {Object} originalEvent - The original DOM event.
|
|
* @property {Boolean} preventDefault - Set to true to prevent the default user-agent's handling of the contextmenu event.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'canvas-contextmenu', eventArgs );
|
|
|
|
event.preventDefault = eventArgs.preventDefault;
|
|
}
|
|
|
|
function onCanvasKeyDown( event ) {
|
|
var canvasKeyDownEventArgs = {
|
|
originalEvent: event.originalEvent,
|
|
preventDefaultAction: false,
|
|
preventVerticalPan: event.preventVerticalPan || !this.panVertical,
|
|
preventHorizontalPan: event.preventHorizontalPan || !this.panHorizontal
|
|
};
|
|
|
|
/**
|
|
* Raised when a keyboard key is pressed and the focus is on the {@link OpenSeadragon.Viewer#canvas} element.
|
|
*
|
|
* @event canvas-key
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
|
* @property {Object} originalEvent - The original DOM event.
|
|
* @property {Boolean} preventDefaultAction - Set to true to prevent default keyboard behaviour. Default: false.
|
|
* @property {Boolean} preventVerticalPan - Set to true to prevent keyboard vertical panning. Default: false.
|
|
* @property {Boolean} preventHorizontalPan - Set to true to prevent keyboard horizontal panning. Default: false.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
|
|
this.raiseEvent('canvas-key', canvasKeyDownEventArgs);
|
|
|
|
if ( !canvasKeyDownEventArgs.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
|
|
switch( event.keyCode ){
|
|
case 38://up arrow
|
|
if (!canvasKeyDownEventArgs.preventVerticalPan) {
|
|
if ( event.shift ) {
|
|
this.viewport.zoomBy(1.1);
|
|
} else {
|
|
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, -this.pixelsPerArrowPress)));
|
|
}
|
|
this.viewport.applyConstraints();
|
|
}
|
|
event.preventDefault = true;
|
|
break;
|
|
case 40://down arrow
|
|
if (!canvasKeyDownEventArgs.preventVerticalPan) {
|
|
if ( event.shift ) {
|
|
this.viewport.zoomBy(0.9);
|
|
} else {
|
|
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, this.pixelsPerArrowPress)));
|
|
}
|
|
this.viewport.applyConstraints();
|
|
}
|
|
event.preventDefault = true;
|
|
break;
|
|
case 37://left arrow
|
|
if (!canvasKeyDownEventArgs.preventHorizontalPan) {
|
|
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(-this.pixelsPerArrowPress, 0)));
|
|
this.viewport.applyConstraints();
|
|
}
|
|
event.preventDefault = true;
|
|
break;
|
|
case 39://right arrow
|
|
if (!canvasKeyDownEventArgs.preventHorizontalPan) {
|
|
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(this.pixelsPerArrowPress, 0)));
|
|
this.viewport.applyConstraints();
|
|
}
|
|
event.preventDefault = true;
|
|
break;
|
|
default:
|
|
//console.log( 'navigator keycode %s', event.keyCode );
|
|
event.preventDefault = false;
|
|
break;
|
|
}
|
|
} else {
|
|
event.preventDefault = false;
|
|
}
|
|
}
|
|
function onCanvasKeyPress( event ) {
|
|
var canvasKeyPressEventArgs = {
|
|
originalEvent: event.originalEvent,
|
|
preventDefaultAction: false,
|
|
preventVerticalPan: event.preventVerticalPan || !this.panVertical,
|
|
preventHorizontalPan: event.preventHorizontalPan || !this.panHorizontal
|
|
};
|
|
|
|
// This event is documented in onCanvasKeyDown
|
|
this.raiseEvent('canvas-key', canvasKeyPressEventArgs);
|
|
|
|
if ( !canvasKeyPressEventArgs.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
|
|
switch( event.keyCode ){
|
|
case 43://=|+
|
|
case 61://=|+
|
|
this.viewport.zoomBy(1.1);
|
|
this.viewport.applyConstraints();
|
|
event.preventDefault = true;
|
|
break;
|
|
case 45://-|_
|
|
this.viewport.zoomBy(0.9);
|
|
this.viewport.applyConstraints();
|
|
event.preventDefault = true;
|
|
break;
|
|
case 48://0|)
|
|
this.viewport.goHome();
|
|
this.viewport.applyConstraints();
|
|
event.preventDefault = true;
|
|
break;
|
|
case 119://w
|
|
case 87://W
|
|
if (!canvasKeyPressEventArgs.preventVerticalPan) {
|
|
if ( event.shift ) {
|
|
this.viewport.zoomBy(1.1);
|
|
} else {
|
|
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, -40)));
|
|
}
|
|
this.viewport.applyConstraints();
|
|
}
|
|
event.preventDefault = true;
|
|
break;
|
|
case 115://s
|
|
case 83://S
|
|
if (!canvasKeyPressEventArgs.preventVerticalPan) {
|
|
if ( event.shift ) {
|
|
this.viewport.zoomBy(0.9);
|
|
} else {
|
|
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, 40)));
|
|
}
|
|
this.viewport.applyConstraints();
|
|
}
|
|
event.preventDefault = true;
|
|
break;
|
|
case 97://a
|
|
if (!canvasKeyPressEventArgs.preventHorizontalPan) {
|
|
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(-40, 0)));
|
|
this.viewport.applyConstraints();
|
|
}
|
|
event.preventDefault = true;
|
|
break;
|
|
case 100://d
|
|
if (!canvasKeyPressEventArgs.preventHorizontalPan) {
|
|
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(40, 0)));
|
|
this.viewport.applyConstraints();
|
|
}
|
|
event.preventDefault = true;
|
|
break;
|
|
case 114: //r - clockwise rotation
|
|
if(this.viewport.flipped){
|
|
this.viewport.setRotation($.positiveModulo(this.viewport.degrees - this.rotationIncrement, 360));
|
|
} else{
|
|
this.viewport.setRotation($.positiveModulo(this.viewport.degrees + this.rotationIncrement, 360));
|
|
}
|
|
this.viewport.applyConstraints();
|
|
event.preventDefault = true;
|
|
break;
|
|
case 82: //R - counterclockwise rotation
|
|
if(this.viewport.flipped){
|
|
this.viewport.setRotation($.positiveModulo(this.viewport.degrees + this.rotationIncrement, 360));
|
|
} else{
|
|
this.viewport.setRotation($.positiveModulo(this.viewport.degrees - this.rotationIncrement, 360));
|
|
}
|
|
this.viewport.applyConstraints();
|
|
event.preventDefault = true;
|
|
break;
|
|
case 102: //f
|
|
this.viewport.toggleFlip();
|
|
event.preventDefault = true;
|
|
break;
|
|
case 106: //j - previous image source
|
|
this.goToPreviousPage();
|
|
break;
|
|
case 107: //k - next image source
|
|
this.goToNextPage();
|
|
break;
|
|
default:
|
|
// console.log( 'navigator keycode %s', event.keyCode );
|
|
event.preventDefault = false;
|
|
break;
|
|
}
|
|
} else {
|
|
event.preventDefault = false;
|
|
}
|
|
}
|
|
|
|
function onCanvasClick( event ) {
|
|
var gestureSettings;
|
|
|
|
var haveKeyboardFocus = document.activeElement === this.canvas;
|
|
|
|
// If we don't have keyboard focus, request it.
|
|
if ( !haveKeyboardFocus ) {
|
|
this.canvas.focus();
|
|
}
|
|
if(this.viewport.flipped){
|
|
event.position.x = this.viewport.getContainerSize().x - event.position.x;
|
|
}
|
|
|
|
var canvasClickEventArgs = {
|
|
tracker: event.eventSource,
|
|
position: event.position,
|
|
quick: event.quick,
|
|
shift: event.shift,
|
|
originalEvent: event.originalEvent,
|
|
originalTarget: event.originalTarget,
|
|
preventDefaultAction: false
|
|
};
|
|
|
|
/**
|
|
* Raised when a mouse press/release or touch/remove occurs on the {@link OpenSeadragon.Viewer#canvas} element.
|
|
*
|
|
* @event canvas-click
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
|
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
|
|
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
|
|
* @property {Boolean} quick - True only if the clickDistThreshold and clickTimeThreshold are both passed. Useful for differentiating between clicks and drags.
|
|
* @property {Boolean} shift - True if the shift key was pressed during this event.
|
|
* @property {Object} originalEvent - The original DOM event.
|
|
* @property {Element} originalTarget - The DOM element clicked on.
|
|
* @property {Boolean} preventDefaultAction - Set to true to prevent default click to zoom behaviour. Default: false.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'canvas-click', canvasClickEventArgs);
|
|
|
|
if ( !canvasClickEventArgs.preventDefaultAction && this.viewport && event.quick ) {
|
|
gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
|
|
if ( gestureSettings.clickToZoom ) {
|
|
this.viewport.zoomBy(
|
|
event.shift ? 1.0 / this.zoomPerClick : this.zoomPerClick,
|
|
gestureSettings.zoomToRefPoint ? this.viewport.pointFromPixel( event.position, true ) : null
|
|
);
|
|
this.viewport.applyConstraints();
|
|
}
|
|
}
|
|
}
|
|
|
|
function onCanvasDblClick( event ) {
|
|
var gestureSettings;
|
|
|
|
var canvasDblClickEventArgs = {
|
|
tracker: event.eventSource,
|
|
position: event.position,
|
|
shift: event.shift,
|
|
originalEvent: event.originalEvent,
|
|
preventDefaultAction: false
|
|
};
|
|
|
|
/**
|
|
* Raised when a double mouse press/release or touch/remove occurs on the {@link OpenSeadragon.Viewer#canvas} element.
|
|
*
|
|
* @event canvas-double-click
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
|
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
|
|
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
|
|
* @property {Boolean} shift - True if the shift key was pressed during this event.
|
|
* @property {Object} originalEvent - The original DOM event.
|
|
* @property {Boolean} preventDefaultAction - Set to true to prevent default double tap to zoom behaviour. Default: false.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'canvas-double-click', canvasDblClickEventArgs);
|
|
|
|
if ( !canvasDblClickEventArgs.preventDefaultAction && this.viewport ) {
|
|
gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
|
|
if ( gestureSettings.dblClickToZoom ) {
|
|
this.viewport.zoomBy(
|
|
event.shift ? 1.0 / this.zoomPerClick : this.zoomPerClick,
|
|
gestureSettings.zoomToRefPoint ? this.viewport.pointFromPixel( event.position, true ) : null
|
|
);
|
|
this.viewport.applyConstraints();
|
|
}
|
|
}
|
|
}
|
|
|
|
function onCanvasDrag( event ) {
|
|
var gestureSettings;
|
|
|
|
var canvasDragEventArgs = {
|
|
tracker: event.eventSource,
|
|
pointerType: event.pointerType,
|
|
position: event.position,
|
|
delta: event.delta,
|
|
speed: event.speed,
|
|
direction: event.direction,
|
|
shift: event.shift,
|
|
originalEvent: event.originalEvent,
|
|
preventDefaultAction: false
|
|
};
|
|
|
|
/**
|
|
* Raised when a mouse or touch drag operation occurs on the {@link OpenSeadragon.Viewer#canvas} element.
|
|
*
|
|
* @event canvas-drag
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
|
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
|
|
* @property {String} pointerType - "mouse", "touch", "pen", etc.
|
|
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
|
|
* @property {OpenSeadragon.Point} delta - The x,y components of the difference between start drag and end drag.
|
|
* @property {Number} speed - Current computed speed, in pixels per second.
|
|
* @property {Number} direction - Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
|
|
* @property {Boolean} shift - True if the shift key was pressed during this event.
|
|
* @property {Object} originalEvent - The original DOM event.
|
|
* @property {Boolean} preventDefaultAction - Set to true to prevent default drag to pan behaviour. Default: false.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'canvas-drag', canvasDragEventArgs);
|
|
|
|
gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
|
|
|
|
if ( gestureSettings.dragToPan && !canvasDragEventArgs.preventDefaultAction && this.viewport ) {
|
|
if( !this.panHorizontal ){
|
|
event.delta.x = 0;
|
|
}
|
|
if( !this.panVertical ){
|
|
event.delta.y = 0;
|
|
}
|
|
if(this.viewport.flipped){
|
|
event.delta.x = -event.delta.x;
|
|
}
|
|
|
|
if( this.constrainDuringPan ){
|
|
var delta = this.viewport.deltaPointsFromPixels( event.delta.negate() );
|
|
|
|
this.viewport.centerSpringX.target.value += delta.x;
|
|
this.viewport.centerSpringY.target.value += delta.y;
|
|
|
|
var bounds = this.viewport.getBounds();
|
|
var constrainedBounds = this.viewport.getConstrainedBounds();
|
|
|
|
this.viewport.centerSpringX.target.value -= delta.x;
|
|
this.viewport.centerSpringY.target.value -= delta.y;
|
|
|
|
if (bounds.x !== constrainedBounds.x) {
|
|
event.delta.x = 0;
|
|
}
|
|
|
|
if (bounds.y !== constrainedBounds.y) {
|
|
event.delta.y = 0;
|
|
}
|
|
}
|
|
|
|
this.viewport.panBy( this.viewport.deltaPointsFromPixels( event.delta.negate() ), gestureSettings.flickEnabled && !this.constrainDuringPan);
|
|
}
|
|
}
|
|
|
|
function onCanvasDragEnd( event ) {
|
|
var canvasDragEndEventArgs = {
|
|
tracker: event.eventSource,
|
|
pointerType: event.pointerType,
|
|
position: event.position,
|
|
speed: event.speed,
|
|
direction: event.direction,
|
|
shift: event.shift,
|
|
originalEvent: event.originalEvent,
|
|
preventDefaultAction: false
|
|
};
|
|
|
|
/**
|
|
* Raised when a mouse or touch drag operation ends on the {@link OpenSeadragon.Viewer#canvas} element.
|
|
*
|
|
* @event canvas-drag-end
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
|
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
|
|
* @property {String} pointerType - "mouse", "touch", "pen", etc.
|
|
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
|
|
* @property {Number} speed - Speed at the end of a drag gesture, in pixels per second.
|
|
* @property {Number} direction - Direction at the end of a drag gesture, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
|
|
* @property {Boolean} shift - True if the shift key was pressed during this event.
|
|
* @property {Object} originalEvent - The original DOM event.
|
|
* @property {Boolean} preventDefaultAction - Set to true to prevent default drag-end flick behaviour. Default: false.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent('canvas-drag-end', canvasDragEndEventArgs);
|
|
|
|
if (!canvasDragEndEventArgs.preventDefaultAction && this.viewport) {
|
|
var gestureSettings = this.gestureSettingsByDeviceType(event.pointerType);
|
|
if (gestureSettings.flickEnabled &&
|
|
event.speed >= gestureSettings.flickMinSpeed) {
|
|
var amplitudeX = 0;
|
|
if (this.panHorizontal) {
|
|
amplitudeX = gestureSettings.flickMomentum * event.speed *
|
|
Math.cos(event.direction);
|
|
}
|
|
var amplitudeY = 0;
|
|
if (this.panVertical) {
|
|
amplitudeY = gestureSettings.flickMomentum * event.speed *
|
|
Math.sin(event.direction);
|
|
}
|
|
var center = this.viewport.pixelFromPoint(
|
|
this.viewport.getCenter(true));
|
|
var target = this.viewport.pointFromPixel(
|
|
new $.Point(center.x - amplitudeX, center.y - amplitudeY));
|
|
this.viewport.panTo(target, false);
|
|
}
|
|
this.viewport.applyConstraints();
|
|
}
|
|
}
|
|
|
|
function onCanvasEnter( event ) {
|
|
/**
|
|
* Raised when a pointer enters the {@link OpenSeadragon.Viewer#canvas} element.
|
|
*
|
|
* @event canvas-enter
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
|
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
|
|
* @property {String} pointerType - "mouse", "touch", "pen", etc.
|
|
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
|
|
* @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.
|
|
* @property {Number} pointers - Number of pointers (all types) active in the tracked element.
|
|
* @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
|
|
* @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
|
|
* @property {Object} originalEvent - The original DOM event.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'canvas-enter', {
|
|
tracker: event.eventSource,
|
|
pointerType: event.pointerType,
|
|
position: event.position,
|
|
buttons: event.buttons,
|
|
pointers: event.pointers,
|
|
insideElementPressed: event.insideElementPressed,
|
|
buttonDownAny: event.buttonDownAny,
|
|
originalEvent: event.originalEvent
|
|
});
|
|
}
|
|
|
|
function onCanvasLeave( event ) {
|
|
/**
|
|
* Raised when a pointer leaves the {@link OpenSeadragon.Viewer#canvas} element.
|
|
*
|
|
* @event canvas-exit
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
|
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
|
|
* @property {String} pointerType - "mouse", "touch", "pen", etc.
|
|
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
|
|
* @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.
|
|
* @property {Number} pointers - Number of pointers (all types) active in the tracked element.
|
|
* @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
|
|
* @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
|
|
* @property {Object} originalEvent - The original DOM event.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'canvas-exit', {
|
|
tracker: event.eventSource,
|
|
pointerType: event.pointerType,
|
|
position: event.position,
|
|
buttons: event.buttons,
|
|
pointers: event.pointers,
|
|
insideElementPressed: event.insideElementPressed,
|
|
buttonDownAny: event.buttonDownAny,
|
|
originalEvent: event.originalEvent
|
|
});
|
|
}
|
|
|
|
function onCanvasPress( event ) {
|
|
/**
|
|
* Raised when the primary mouse button is pressed or touch starts on the {@link OpenSeadragon.Viewer#canvas} element.
|
|
*
|
|
* @event canvas-press
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
|
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
|
|
* @property {String} pointerType - "mouse", "touch", "pen", etc.
|
|
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
|
|
* @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
|
|
* @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released.
|
|
* @property {Object} originalEvent - The original DOM event.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'canvas-press', {
|
|
tracker: event.eventSource,
|
|
pointerType: event.pointerType,
|
|
position: event.position,
|
|
insideElementPressed: event.insideElementPressed,
|
|
insideElementReleased: event.insideElementReleased,
|
|
originalEvent: event.originalEvent
|
|
});
|
|
}
|
|
|
|
function onCanvasRelease( event ) {
|
|
/**
|
|
* Raised when the primary mouse button is released or touch ends on the {@link OpenSeadragon.Viewer#canvas} element.
|
|
*
|
|
* @event canvas-release
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
|
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
|
|
* @property {String} pointerType - "mouse", "touch", "pen", etc.
|
|
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
|
|
* @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
|
|
* @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released.
|
|
* @property {Object} originalEvent - The original DOM event.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'canvas-release', {
|
|
tracker: event.eventSource,
|
|
pointerType: event.pointerType,
|
|
position: event.position,
|
|
insideElementPressed: event.insideElementPressed,
|
|
insideElementReleased: event.insideElementReleased,
|
|
originalEvent: event.originalEvent
|
|
});
|
|
}
|
|
|
|
function onCanvasNonPrimaryPress( event ) {
|
|
/**
|
|
* Raised when any non-primary pointer button is pressed on the {@link OpenSeadragon.Viewer#canvas} element.
|
|
*
|
|
* @event canvas-nonprimary-press
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
|
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
|
|
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
|
|
* @property {String} pointerType - "mouse", "touch", "pen", etc.
|
|
* @property {Number} button - Button which caused the event.
|
|
* -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
|
|
* @property {Number} buttons - Current buttons pressed.
|
|
* 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.
|
|
* @property {Object} originalEvent - The original DOM event.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'canvas-nonprimary-press', {
|
|
tracker: event.eventSource,
|
|
position: event.position,
|
|
pointerType: event.pointerType,
|
|
button: event.button,
|
|
buttons: event.buttons,
|
|
originalEvent: event.originalEvent
|
|
});
|
|
}
|
|
|
|
function onCanvasNonPrimaryRelease( event ) {
|
|
/**
|
|
* Raised when any non-primary pointer button is released on the {@link OpenSeadragon.Viewer#canvas} element.
|
|
*
|
|
* @event canvas-nonprimary-release
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
|
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
|
|
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
|
|
* @property {String} pointerType - "mouse", "touch", "pen", etc.
|
|
* @property {Number} button - Button which caused the event.
|
|
* -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
|
|
* @property {Number} buttons - Current buttons pressed.
|
|
* 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.
|
|
* @property {Object} originalEvent - The original DOM event.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'canvas-nonprimary-release', {
|
|
tracker: event.eventSource,
|
|
position: event.position,
|
|
pointerType: event.pointerType,
|
|
button: event.button,
|
|
buttons: event.buttons,
|
|
originalEvent: event.originalEvent
|
|
});
|
|
}
|
|
|
|
function onCanvasPinch( event ) {
|
|
var gestureSettings,
|
|
centerPt,
|
|
lastCenterPt,
|
|
panByPt;
|
|
|
|
var canvasPinchEventArgs = {
|
|
tracker: event.eventSource,
|
|
pointerType: event.pointerType,
|
|
gesturePoints: event.gesturePoints,
|
|
lastCenter: event.lastCenter,
|
|
center: event.center,
|
|
lastDistance: event.lastDistance,
|
|
distance: event.distance,
|
|
shift: event.shift,
|
|
originalEvent: event.originalEvent,
|
|
preventDefaultPanAction: false,
|
|
preventDefaultZoomAction: false,
|
|
preventDefaultRotateAction: false
|
|
};
|
|
|
|
/**
|
|
* Raised when a pinch event occurs on the {@link OpenSeadragon.Viewer#canvas} element.
|
|
*
|
|
* @event canvas-pinch
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
|
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
|
|
* @property {String} pointerType - "mouse", "touch", "pen", etc.
|
|
* @property {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gesturePoints - Gesture points associated with the gesture. Velocity data can be found here.
|
|
* @property {OpenSeadragon.Point} lastCenter - The previous center point of the two pinch contact points relative to the tracked element.
|
|
* @property {OpenSeadragon.Point} center - The center point of the two pinch contact points relative to the tracked element.
|
|
* @property {Number} lastDistance - The previous distance between the two pinch contact points in CSS pixels.
|
|
* @property {Number} distance - The distance between the two pinch contact points in CSS pixels.
|
|
* @property {Boolean} shift - True if the shift key was pressed during this event.
|
|
* @property {Object} originalEvent - The original DOM event.
|
|
* @property {Boolean} preventDefaultPanAction - Set to true to prevent default pinch to pan behaviour. Default: false.
|
|
* @property {Boolean} preventDefaultZoomAction - Set to true to prevent default pinch to zoom behaviour. Default: false.
|
|
* @property {Boolean} preventDefaultRotateAction - Set to true to prevent default pinch to rotate behaviour. Default: false.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent('canvas-pinch', canvasPinchEventArgs);
|
|
|
|
if ( this.viewport ) {
|
|
gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
|
|
if ( gestureSettings.pinchToZoom &&
|
|
(!canvasPinchEventArgs.preventDefaultPanAction || !canvasPinchEventArgs.preventDefaultZoomAction) ) {
|
|
centerPt = this.viewport.pointFromPixel( event.center, true );
|
|
if ( gestureSettings.zoomToRefPoint && !canvasPinchEventArgs.preventDefaultPanAction ) {
|
|
lastCenterPt = this.viewport.pointFromPixel( event.lastCenter, true );
|
|
panByPt = lastCenterPt.minus( centerPt );
|
|
if( !this.panHorizontal ) {
|
|
panByPt.x = 0;
|
|
}
|
|
if( !this.panVertical ) {
|
|
panByPt.y = 0;
|
|
}
|
|
this.viewport.panBy(panByPt, true);
|
|
}
|
|
if ( !canvasPinchEventArgs.preventDefaultZoomAction ) {
|
|
this.viewport.zoomBy( event.distance / event.lastDistance, centerPt, true );
|
|
}
|
|
this.viewport.applyConstraints();
|
|
}
|
|
if ( gestureSettings.pinchRotate && !canvasPinchEventArgs.preventDefaultRotateAction ) {
|
|
// Pinch rotate
|
|
var angle1 = Math.atan2(event.gesturePoints[0].currentPos.y - event.gesturePoints[1].currentPos.y,
|
|
event.gesturePoints[0].currentPos.x - event.gesturePoints[1].currentPos.x);
|
|
var angle2 = Math.atan2(event.gesturePoints[0].lastPos.y - event.gesturePoints[1].lastPos.y,
|
|
event.gesturePoints[0].lastPos.x - event.gesturePoints[1].lastPos.x);
|
|
this.viewport.setRotation(this.viewport.getRotation() + ((angle1 - angle2) * (180 / Math.PI)));
|
|
}
|
|
}
|
|
}
|
|
|
|
function onCanvasScroll( event ) {
|
|
var canvasScrollEventArgs,
|
|
gestureSettings,
|
|
factor,
|
|
thisScrollTime,
|
|
deltaScrollTime;
|
|
|
|
/* Certain scroll devices fire the scroll event way too fast so we are injecting a simple adjustment to keep things
|
|
* partially normalized. If we have already fired an event within the last 'minScrollDelta' milliseconds we skip
|
|
* this one and wait for the next event. */
|
|
thisScrollTime = $.now();
|
|
deltaScrollTime = thisScrollTime - this._lastScrollTime;
|
|
if (deltaScrollTime > this.minScrollDeltaTime) {
|
|
this._lastScrollTime = thisScrollTime;
|
|
|
|
canvasScrollEventArgs = {
|
|
tracker: event.eventSource,
|
|
position: event.position,
|
|
scroll: event.scroll,
|
|
shift: event.shift,
|
|
originalEvent: event.originalEvent,
|
|
preventDefaultAction: false,
|
|
preventDefault: true
|
|
};
|
|
|
|
/**
|
|
* Raised when a scroll event occurs on the {@link OpenSeadragon.Viewer#canvas} element (mouse wheel).
|
|
*
|
|
* @event canvas-scroll
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
|
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
|
|
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
|
|
* @property {Number} scroll - The scroll delta for the event.
|
|
* @property {Boolean} shift - True if the shift key was pressed during this event.
|
|
* @property {Object} originalEvent - The original DOM event.
|
|
* @property {Boolean} preventDefaultAction - Set to true to prevent default scroll to zoom behaviour. Default: false.
|
|
* @property {Boolean} preventDefault - Set to true to prevent the default user-agent's handling of the wheel event. Default: true.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent('canvas-scroll', canvasScrollEventArgs );
|
|
|
|
if ( !canvasScrollEventArgs.preventDefaultAction && this.viewport ) {
|
|
if(this.viewport.flipped){
|
|
event.position.x = this.viewport.getContainerSize().x - event.position.x;
|
|
}
|
|
|
|
gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
|
|
if ( gestureSettings.scrollToZoom ) {
|
|
factor = Math.pow( this.zoomPerScroll, event.scroll );
|
|
this.viewport.zoomBy(
|
|
factor,
|
|
gestureSettings.zoomToRefPoint ? this.viewport.pointFromPixel( event.position, true ) : null
|
|
);
|
|
this.viewport.applyConstraints();
|
|
}
|
|
}
|
|
|
|
event.preventDefault = canvasScrollEventArgs.preventDefault;
|
|
} else {
|
|
event.preventDefault = true;
|
|
}
|
|
}
|
|
|
|
function onContainerEnter( event ) {
|
|
THIS[ this.hash ].mouseInside = true;
|
|
abortControlsAutoHide( this );
|
|
/**
|
|
* Raised when the cursor enters the {@link OpenSeadragon.Viewer#container} element.
|
|
*
|
|
* @event container-enter
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
|
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
|
|
* @property {String} pointerType - "mouse", "touch", "pen", etc.
|
|
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
|
|
* @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.
|
|
* @property {Number} pointers - Number of pointers (all types) active in the tracked element.
|
|
* @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
|
|
* @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
|
|
* @property {Object} originalEvent - The original DOM event.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'container-enter', {
|
|
tracker: event.eventSource,
|
|
pointerType: event.pointerType,
|
|
position: event.position,
|
|
buttons: event.buttons,
|
|
pointers: event.pointers,
|
|
insideElementPressed: event.insideElementPressed,
|
|
buttonDownAny: event.buttonDownAny,
|
|
originalEvent: event.originalEvent
|
|
});
|
|
}
|
|
|
|
function onContainerLeave( event ) {
|
|
if ( event.pointers < 1 ) {
|
|
THIS[ this.hash ].mouseInside = false;
|
|
if ( !THIS[ this.hash ].animating ) {
|
|
beginControlsAutoHide( this );
|
|
}
|
|
}
|
|
/**
|
|
* Raised when the cursor leaves the {@link OpenSeadragon.Viewer#container} element.
|
|
*
|
|
* @event container-exit
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
|
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
|
|
* @property {String} pointerType - "mouse", "touch", "pen", etc.
|
|
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
|
|
* @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.
|
|
* @property {Number} pointers - Number of pointers (all types) active in the tracked element.
|
|
* @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
|
|
* @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
|
|
* @property {Object} originalEvent - The original DOM event.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
this.raiseEvent( 'container-exit', {
|
|
tracker: event.eventSource,
|
|
pointerType: event.pointerType,
|
|
position: event.position,
|
|
buttons: event.buttons,
|
|
pointers: event.pointers,
|
|
insideElementPressed: event.insideElementPressed,
|
|
buttonDownAny: event.buttonDownAny,
|
|
originalEvent: event.originalEvent
|
|
});
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Page update routines ( aka Views - for future reference )
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
function updateMulti( viewer ) {
|
|
updateOnce( viewer );
|
|
|
|
// Request the next frame, unless we've been closed
|
|
if ( viewer.isOpen() ) {
|
|
viewer._updateRequestId = scheduleUpdate( viewer, updateMulti );
|
|
} else {
|
|
viewer._updateRequestId = false;
|
|
}
|
|
}
|
|
|
|
function updateOnce( viewer ) {
|
|
|
|
//viewer.profiler.beginUpdate();
|
|
|
|
if (viewer._opening || !THIS[viewer.hash]) {
|
|
return;
|
|
}
|
|
|
|
if (viewer.autoResize) {
|
|
var containerSize = _getSafeElemSize(viewer.container);
|
|
var prevContainerSize = THIS[viewer.hash].prevContainerSize;
|
|
if (!containerSize.equals(prevContainerSize)) {
|
|
var viewport = viewer.viewport;
|
|
if (viewer.preserveImageSizeOnResize) {
|
|
var resizeRatio = prevContainerSize.x / containerSize.x;
|
|
var zoom = viewport.getZoom() * resizeRatio;
|
|
var center = viewport.getCenter();
|
|
viewport.resize(containerSize, false);
|
|
viewport.zoomTo(zoom, null, true);
|
|
viewport.panTo(center, true);
|
|
} else {
|
|
// maintain image position
|
|
var oldBounds = viewport.getBounds();
|
|
viewport.resize(containerSize, true);
|
|
viewport.fitBoundsWithConstraints(oldBounds, true);
|
|
}
|
|
THIS[viewer.hash].prevContainerSize = containerSize;
|
|
THIS[viewer.hash].forceRedraw = true;
|
|
}
|
|
}
|
|
|
|
var viewportChange = viewer.viewport.update();
|
|
var animated = viewer.world.update() || viewportChange;
|
|
|
|
if (viewportChange) {
|
|
/**
|
|
* Raised when any spring animation update occurs (zoom, pan, etc.),
|
|
* before the viewer has drawn the new location.
|
|
*
|
|
* @event viewport-change
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
viewer.raiseEvent('viewport-change');
|
|
}
|
|
|
|
if( viewer.referenceStrip ){
|
|
animated = viewer.referenceStrip.update( viewer.viewport ) || animated;
|
|
}
|
|
|
|
var currentAnimating = THIS[ viewer.hash ].animating;
|
|
|
|
if ( !currentAnimating && animated ) {
|
|
/**
|
|
* Raised when any spring animation starts (zoom, pan, etc.).
|
|
*
|
|
* @event animation-start
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
viewer.raiseEvent( "animation-start" );
|
|
abortControlsAutoHide( viewer );
|
|
}
|
|
|
|
var isAnimationFinished = currentAnimating && !animated;
|
|
|
|
if ( isAnimationFinished ) {
|
|
THIS[ viewer.hash ].animating = false;
|
|
}
|
|
|
|
if ( animated || isAnimationFinished || THIS[ viewer.hash ].forceRedraw || viewer.world.needsDraw() ) {
|
|
drawWorld( viewer );
|
|
viewer._drawOverlays();
|
|
if( viewer.navigator ){
|
|
viewer.navigator.update( viewer.viewport );
|
|
}
|
|
|
|
THIS[ viewer.hash ].forceRedraw = false;
|
|
|
|
if (animated) {
|
|
/**
|
|
* Raised when any spring animation update occurs (zoom, pan, etc.),
|
|
* after the viewer has drawn the new location.
|
|
*
|
|
* @event animation
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
viewer.raiseEvent( "animation" );
|
|
}
|
|
}
|
|
|
|
if ( isAnimationFinished ) {
|
|
/**
|
|
* Raised when any spring animation ends (zoom, pan, etc.).
|
|
*
|
|
* @event animation-finish
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
viewer.raiseEvent( "animation-finish" );
|
|
|
|
if ( !THIS[ viewer.hash ].mouseInside ) {
|
|
beginControlsAutoHide( viewer );
|
|
}
|
|
}
|
|
|
|
THIS[ viewer.hash ].animating = animated;
|
|
|
|
//viewer.profiler.endUpdate();
|
|
}
|
|
|
|
function drawWorld( viewer ) {
|
|
viewer.imageLoader.clear();
|
|
viewer.drawer.clear();
|
|
viewer.world.draw();
|
|
|
|
/**
|
|
* <em>- Needs documentation -</em>
|
|
*
|
|
* @event update-viewport
|
|
* @memberof OpenSeadragon.Viewer
|
|
* @type {object}
|
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
|
*/
|
|
viewer.raiseEvent( 'update-viewport', {} );
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Navigation Controls
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
function resolveUrl( prefix, url ) {
|
|
return prefix ? prefix + url : url;
|
|
}
|
|
|
|
|
|
|
|
function beginZoomingIn() {
|
|
THIS[ this.hash ].lastZoomTime = $.now();
|
|
THIS[ this.hash ].zoomFactor = this.zoomPerSecond;
|
|
THIS[ this.hash ].zooming = true;
|
|
scheduleZoom( this );
|
|
}
|
|
|
|
|
|
function beginZoomingOut() {
|
|
THIS[ this.hash ].lastZoomTime = $.now();
|
|
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 ) {
|
|
$.requestAnimationFrame( $.delegate( viewer, doZoom ) );
|
|
}
|
|
|
|
|
|
function doZoom() {
|
|
var currentTime,
|
|
deltaTime,
|
|
adjustedFactor;
|
|
|
|
if ( THIS[ this.hash ].zooming && this.viewport) {
|
|
currentTime = $.now();
|
|
deltaTime = currentTime - THIS[ this.hash ].lastZoomTime;
|
|
adjustedFactor = Math.pow( THIS[ this.hash ].zoomFactor, deltaTime / 1000 );
|
|
|
|
this.viewport.zoomBy( adjustedFactor );
|
|
this.viewport.applyConstraints();
|
|
THIS[ this.hash ].lastZoomTime = currentTime;
|
|
scheduleZoom( this );
|
|
}
|
|
}
|
|
|
|
|
|
function doSingleZoomIn() {
|
|
if ( this.viewport ) {
|
|
THIS[ this.hash ].zooming = false;
|
|
this.viewport.zoomBy(
|
|
this.zoomPerClick / 1.0
|
|
);
|
|
this.viewport.applyConstraints();
|
|
}
|
|
}
|
|
|
|
|
|
function doSingleZoomOut() {
|
|
if ( this.viewport ) {
|
|
THIS[ this.hash ].zooming = false;
|
|
this.viewport.zoomBy(
|
|
1.0 / this.zoomPerClick
|
|
);
|
|
this.viewport.applyConstraints();
|
|
}
|
|
}
|
|
|
|
|
|
function lightUp() {
|
|
if (this.buttonGroup) {
|
|
this.buttonGroup.emulateEnter();
|
|
this.buttonGroup.emulateLeave();
|
|
}
|
|
}
|
|
|
|
|
|
function onHome() {
|
|
if ( this.viewport ) {
|
|
this.viewport.goHome();
|
|
}
|
|
}
|
|
|
|
|
|
function onFullScreen() {
|
|
if ( this.isFullPage() && !$.isFullScreen() ) {
|
|
// Is fullPage but not fullScreen
|
|
this.setFullPage( false );
|
|
} else {
|
|
this.setFullScreen( !this.isFullPage() );
|
|
}
|
|
// correct for no mouseout event on change
|
|
if ( this.buttonGroup ) {
|
|
this.buttonGroup.emulateLeave();
|
|
}
|
|
this.fullPageButton.element.focus();
|
|
if ( this.viewport ) {
|
|
this.viewport.applyConstraints();
|
|
}
|
|
}
|
|
|
|
function onRotateLeft() {
|
|
if ( this.viewport ) {
|
|
var currRotation = this.viewport.getRotation();
|
|
|
|
if ( this.viewport.flipped ){
|
|
currRotation = $.positiveModulo(currRotation + this.rotationIncrement, 360);
|
|
} else {
|
|
currRotation = $.positiveModulo(currRotation - this.rotationIncrement, 360);
|
|
}
|
|
this.viewport.setRotation(currRotation);
|
|
}
|
|
}
|
|
|
|
function onRotateRight() {
|
|
if ( this.viewport ) {
|
|
var currRotation = this.viewport.getRotation();
|
|
|
|
if ( this.viewport.flipped ){
|
|
currRotation = $.positiveModulo(currRotation - this.rotationIncrement, 360);
|
|
} else {
|
|
currRotation = $.positiveModulo(currRotation + this.rotationIncrement, 360);
|
|
}
|
|
this.viewport.setRotation(currRotation);
|
|
}
|
|
}
|
|
/**
|
|
* Note: When pressed flip control button
|
|
*/
|
|
function onFlip() {
|
|
this.viewport.toggleFlip();
|
|
}
|
|
|
|
}( OpenSeadragon ));
|