diff --git a/changelog.txt b/changelog.txt index 44c73777..7a03311c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,23 +4,24 @@ OPENSEADRAGON CHANGELOG 1.0.0: (in progress) * BREAKING CHANGE: All EventSource and MouseTracker event handler method signatures changed to 'handlerMethod(event)' where event == { eventSource, userData, ... } (#251) (Also fixes #23, #224, #239) -* The new eventSource property in the event object replaces the old eventSource parameter that was passed to handler methods. -* Where the event object duplicated the eventSource value, those properties have been removed. This effects the following events: -* All Button events - 'button' property removed -* All Viewer (Viewer, Drawer, Viewport) events - 'viewer' property removed + * The new eventSource property in the event object replaces the old eventSource parameter that was passed to handler methods. + * Where the event object duplicated the eventSource value, those properties have been removed. This affects the following events: + * All Button events - 'button' property removed + * All Viewer (Viewer, Drawer, Viewport) events - 'viewer' property removed * BREAKING CHANGE: Renamed EventHandler to EventSource (#225) * BREAKING CHANGE: Event names changed for consistency: changed to lower case, compound names hyphenated, and "on" prefixes removed (#226): -* Viewer "animationstart" changed to "animation-start" -* Viewer "animationfinish" changed to "animation-finish" -* Button "onPress" changed to "press" -* Button "onRelease" changed to "release" -* Button "onClick" changed to "click" -* Button "onEnter" changed to "enter" -* Button "onExit" changed to "exit" -* Button "onFocus" changed to "focus" -* Button "onBlur" changed to "blur" + * Viewer "animationstart" changed to "animation-start" + * Viewer "animationfinish" changed to "animation-finish" + * Button "onPress" changed to "press" + * Button "onRelease" changed to "release" + * Button "onClick" changed to "click" + * Button "onEnter" changed to "enter" + * Button "onExit" changed to "exit" + * Button "onFocus" changed to "focus" + * Button "onBlur" changed to "blur" * MouseTracker now passes the original event objects to its handler methods (#23) * MouseTracker now supports an optional 'moveHandler' method for tracking mousemove events (#215) +* Added stopHandler to MouseTracker. (#262) * Fixed: Element-relative mouse coordinates now correct if the element and/or page is scrolled (using new OpenSeadragon.getElementOffset() method) (#131) * Fixed: Pinch zoom event issue, regressive issue from previous event system changes (#244) * Added IIIF Image API 1.1 Tile Source (#230) @@ -29,6 +30,20 @@ OPENSEADRAGON CHANGELOG * Check that zoom reference point is valid before using it in zoomTo and zoomBy (#247) * Added a number of easier coordinate conversion methods to viewport (#243) * Added the ability to create a viewer and start at a specified page (#252) +* Fixed image resolve issue with collection mode (#255) +* DOM events are now passed through as 'event.originalEvent' in viewer and button events where appropriate. (#257) Affects the following events: + * Viewer: 'canvas-release', 'canvas-click', 'canvas-drag', 'canvas-scroll', 'container-enter', 'container-exit', 'container-release' + * Button: 'enter', 'exit', 'press', 'release', 'focus', 'blur', 'click' +* Fixed: IE 10 not reading DZI file correctly in certain circumstances (#218) +* Added support for the 'wheel' DOM mousewheel event (#261) +* Fix for non-canvas tile rendering at large size (#264) +* Drawer now uses an HTML5 canvas element whenever it's available. Can be overridden with the Viewer.useCanvas option (#191) +* Added a boolean preventDefaultAction property (default false) to the event object passed to MouseTracker handler methods. (#270) Implemented in the following MouseTracker subscribers: + * Viewer.keyboardCommandArea.innerTracker.focusHandler: preventDefaultAction == true prevents scrolling viewer into view + * Viewer.keyboardCommandArea.innerTracker.keyHandler: preventDefaultAction == true prevents viewer keyboard navigation + * Viewer.innerTracker.clickHandler: preventDefaultAction == true prevents viewer zoom on click + * Viewer.innerTracker.dragHandler: preventDefaultAction == true prevents viewer panning with mouse/touch + * Viewer.innerTracker.scrollHandler: preventDefaultAction == true prevents viewer zooming on mousewheel/pinch 0.9.131: diff --git a/src/button.js b/src/button.js index e32ca35a..8a09cdb0 100644 --- a/src/button.js +++ b/src/button.js @@ -178,7 +178,7 @@ $.Button = function( options ) { enterHandler: function( event ) { if ( event.insideElementPressed ) { inTo( _this, $.ButtonState.DOWN ); - _this.raiseEvent( "enter", {} ); + _this.raiseEvent( "enter", { originalEvent: event.originalEvent } ); } else if ( !event.buttonDownAny ) { inTo( _this, $.ButtonState.HOVER ); } @@ -186,30 +186,30 @@ $.Button = function( options ) { focusHandler: function ( event ) { this.enterHandler( event ); - _this.raiseEvent( "focus", {} ); + _this.raiseEvent( "focus", { originalEvent: event.originalEvent } ); }, exitHandler: function( event ) { outTo( _this, $.ButtonState.GROUP ); if ( event.insideElementPressed ) { - _this.raiseEvent( "exit", {} ); + _this.raiseEvent( "exit", { originalEvent: event.originalEvent } ); } }, blurHandler: function ( event ) { this.exitHandler( event ); - _this.raiseEvent( "blur", {} ); + _this.raiseEvent( "blur", { originalEvent: event.originalEvent } ); }, pressHandler: function ( event ) { inTo( _this, $.ButtonState.DOWN ); - _this.raiseEvent( "press", {} ); + _this.raiseEvent( "press", { originalEvent: event.originalEvent } ); }, releaseHandler: function( event ) { if ( event.insideElementPressed && event.insideElementReleased ) { outTo( _this, $.ButtonState.HOVER ); - _this.raiseEvent( "release", {} ); + _this.raiseEvent( "release", { originalEvent: event.originalEvent } ); } else if ( event.insideElementPressed ) { outTo( _this, $.ButtonState.GROUP ); } else { @@ -219,15 +219,15 @@ $.Button = function( options ) { clickHandler: function( event ) { if ( event.quick ) { - _this.raiseEvent("click", {}); + _this.raiseEvent("click", { originalEvent: event.originalEvent }); } }, keyHandler: function( event ){ //console.log( "%s : handling key %s!", _this.tooltip, event.keyCode); if( 13 === event.keyCode ){ - _this.raiseEvent( "click", {} ); - _this.raiseEvent( "release", {} ); + _this.raiseEvent( "click", { originalEvent: event.originalEvent } ); + _this.raiseEvent( "release", { originalEvent: event.originalEvent } ); return false; } return true; diff --git a/src/drawer.js b/src/drawer.js index 809d9176..5a049469 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -44,14 +44,8 @@ var DEVICE_SCREEN = $.getWindowSize(), ( BROWSER == $.BROWSERS.SAFARI && BROWSER_VERSION >= 4 ) || ( BROWSER == $.BROWSERS.CHROME && BROWSER_VERSION >= 2 ) || ( BROWSER == $.BROWSERS.IE && BROWSER_VERSION >= 9 ) - ), + ); - USE_CANVAS = SUBPIXEL_RENDERING && - !( DEVICE_SCREEN.x <= 400 || DEVICE_SCREEN.y <= 400 ) && - !( navigator.appVersion.match( 'Mobile' ) ) && - $.isFunction( document.createElement( "canvas" ).getContext ); - -//console.error( 'USE_CANVAS ' + USE_CANVAS ); /** * @class @@ -124,9 +118,10 @@ $.Drawer = function( options ) { }, options ); + this.useCanvas = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true ); this.container = $.getElement( this.element ); - this.canvas = $.makeNeutralElement( USE_CANVAS ? "canvas" : "div" ); - this.context = USE_CANVAS ? this.canvas.getContext( "2d" ) : null; + this.canvas = $.makeNeutralElement( this.useCanvas ? "canvas" : "div" ); + this.context = this.useCanvas ? this.canvas.getContext( "2d" ) : null; this.normHeight = this.source.dimensions.y / this.source.dimensions.x; this.element = this.container; @@ -405,7 +400,7 @@ $.Drawer.prototype = { }, canRotate: function() { - return USE_CANVAS; + return this.useCanvas; } }; @@ -522,7 +517,7 @@ function updateViewport( drawer ) { //TODO drawer.canvas.innerHTML = ""; - if ( USE_CANVAS ) { + if ( drawer.useCanvas ) { if( drawer.canvas.width != viewportSize.x || drawer.canvas.height != viewportSize.y ){ drawer.canvas.width = viewportSize.x; @@ -1160,6 +1155,7 @@ function drawTiles( drawer, lastDrawn ){ //$.console.log("Rendering collection tile %s | %s | %s", tile.y, tile.y, position); if( tileSource ){ drawer.collectionOverlays[ tileKey ] = viewer = new $.Viewer({ + hash: viewport.viewer.hash + "-" + tileKey, element: $.makeNeutralElement( "div" ), mouseNavEnabled: false, showNavigator: false, @@ -1199,7 +1195,7 @@ function drawTiles( drawer, lastDrawn ){ } else { - if ( USE_CANVAS ) { + if ( drawer.useCanvas ) { // TODO do this in a more performant way // specifically, don't save,rotate,restore every time we draw a tile if( drawer.viewport.degrees !== 0 ) { @@ -1262,7 +1258,7 @@ function restoreRotationChanges( tile, canvas, context ){ function drawDebugInfo( drawer, tile, count, i ){ - if ( USE_CANVAS ) { + if ( drawer.useCanvas ) { drawer.context.save(); drawer.context.lineWidth = 2; drawer.context.font = 'small-caps bold 13px ariel'; diff --git a/src/mousetracker.js b/src/mousetracker.js index e0b9e931..c2a7d446 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -59,11 +59,14 @@ * A reference to an element or an element id for which the mouse * events will be monitored. * @param {Number} options.clickTimeThreshold - * The number of milliseconds within which mutliple mouse clicks + * The number of milliseconds within which multiple mouse clicks * will be treated as a single event. * @param {Number} options.clickDistThreshold * The distance between mouse click within multiple mouse clicks * will be treated as a single event. + * @param {Number} options.stopDelay + * The number of milliseconds without mouse move before the mouse stop + * event is fired. * @param {Function} options.enterHandler * An optional handler for mouse enter. * @param {Function} options.exitHandler @@ -116,6 +119,7 @@ this.clickTimeThreshold = options.clickTimeThreshold; this.clickDistThreshold = options.clickDistThreshold; this.userData = options.userData || null; + this.stopDelay = options.stopDelay || 50; this.enterHandler = options.enterHandler || null; this.exitHandler = options.exitHandler || null; @@ -125,6 +129,7 @@ this.scrollHandler = options.scrollHandler || null; this.clickHandler = options.clickHandler || null; this.dragHandler = options.dragHandler || null; + this.stopHandler = options.stopHandler || null; this.keyHandler = options.keyHandler || null; this.focusHandler = options.focusHandler || null; this.blurHandler = options.blurHandler || null; @@ -157,8 +162,10 @@ mouseup: function ( event ) { onMouseUp( _this, event, false ); }, mousemove: function ( event ) { onMouseMove( _this, event ); }, click: function ( event ) { onMouseClick( _this, event ); }, - DOMMouseScroll: function ( event ) { onMouseWheelSpin( _this, event, false ); }, - mousewheel: function ( event ) { onMouseWheelSpin( _this, event, false ); }, + wheel: function ( event ) { onWheel( _this, event ); }, + mousewheel: function ( event ) { onMouseWheel( _this, event ); }, + DOMMouseScroll: function ( event ) { onMouseWheel( _this, event ); }, + MozMousePixelScroll: function ( event ) { onMouseWheel( _this, event ); }, mouseupie: function ( event ) { onMouseUpIE( _this, event ); }, mousemovecapturedie: function ( event ) { onMouseMoveCapturedIE( _this, event ); }, mouseupcaptured: function ( event ) { onMouseUpCaptured( _this, event ); }, @@ -219,7 +226,7 @@ }, /** - * Implement or assign implmentation to these handlers during or after + * Implement or assign implementation to these handlers during or after * calling the constructor. * @function * @param {Object} event @@ -236,13 +243,15 @@ * True if the original event is a touch event, otherwise false. * @param {Object} event.originalEvent * The original event object. + * @param {Boolean} [event.preventDefaultAction=false] + * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). * @param {Object} event.userData * Arbitrary user-defined object. */ enterHandler: function () { }, /** - * Implement or assign implmentation to these handlers during or after + * Implement or assign implementation to these handlers during or after * calling the constructor. * @function * @param {Object} event @@ -259,13 +268,15 @@ * True if the original event is a touch event, otherwise false. * @param {Object} event.originalEvent * The original event object. + * @param {Boolean} [event.preventDefaultAction=false] + * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). * @param {Object} event.userData * Arbitrary user-defined object. */ exitHandler: function () { }, /** - * Implement or assign implmentation to these handlers during or after + * Implement or assign implementation to these handlers during or after * calling the constructor. * @function * @param {Object} event @@ -277,13 +288,15 @@ * True if the original event is a touch event, otherwise false. * @param {Object} event.originalEvent * The original event object. + * @param {Boolean} [event.preventDefaultAction=false] + * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). * @param {Object} event.userData * Arbitrary user-defined object. */ pressHandler: function () { }, /** - * Implement or assign implmentation to these handlers during or after + * Implement or assign implementation to these handlers during or after * calling the constructor. * @function * @param {Object} event @@ -300,13 +313,15 @@ * True if the original event is a touch event, otherwise false. * @param {Object} event.originalEvent * The original event object. + * @param {Boolean} [event.preventDefaultAction=false] + * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). * @param {Object} event.userData * Arbitrary user-defined object. */ releaseHandler: function () { }, /** - * Implement or assign implmentation to these handlers during or after + * Implement or assign implementation to these handlers during or after * calling the constructor. * @function * @param {Object} event @@ -318,13 +333,15 @@ * True if the original event is a touch event, otherwise false. * @param {Object} event.originalEvent * The original event object. + * @param {Boolean} [event.preventDefaultAction=false] + * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). * @param {Object} event.userData * Arbitrary user-defined object. */ moveHandler: function () { }, /** - * Implement or assign implmentation to these handlers during or after + * Implement or assign implementation to these handlers during or after * calling the constructor. * @function * @param {Object} event @@ -340,13 +357,15 @@ * True if the original event is a touch event, otherwise false. * @param {Object} event.originalEvent * The original event object. + * @param {Boolean} [event.preventDefaultAction=false] + * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). * @param {Object} event.userData * Arbitrary user-defined object. */ scrollHandler: function () { }, /** - * Implement or assign implmentation to these handlers during or after + * Implement or assign implementation to these handlers during or after * calling the constructor. * @function * @param {Object} event @@ -362,13 +381,15 @@ * True if the original event is a touch event, otherwise false. * @param {Object} event.originalEvent * The original event object. + * @param {Boolean} [event.preventDefaultAction=false] + * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). * @param {Object} event.userData * Arbitrary user-defined object. */ clickHandler: function () { }, /** - * Implement or assign implmentation to these handlers during or after + * Implement or assign implementation to these handlers during or after * calling the constructor. * @function * @param {Object} event @@ -384,13 +405,35 @@ * True if the original event is a touch event, otherwise false. * @param {Object} event.originalEvent * The original event object. + * @param {Boolean} [event.preventDefaultAction=false] + * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). * @param {Object} event.userData * Arbitrary user-defined object. */ dragHandler: function () { }, /** - * Implement or assign implmentation to these handlers during or after + * Implement or assign implementation to these handlers during or after + * calling the constructor. + * @function + * @param {Object} event + * @param {OpenSeadragon.MouseTracker} event.eventSource + * A reference to the tracker instance. + * @param {OpenSeadragon.Point} event.position + * The position of the event relative to the tracked element. + * @param {Boolean} event.isTouchEvent + * True if the original event is a touch event, otherwise false. + * @param {Object} event.originalEvent + * The original event object. + * @param {Boolean} [event.preventDefaultAction=false] + * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). + * @param {Object} event.userData + * Arbitrary user-defined object. + */ + stopHandler: function () { }, + + /** + * Implement or assign implementation to these handlers during or after * calling the constructor. * @function * @param {Object} event @@ -402,13 +445,15 @@ * True if the shift key was pressed during this event. * @param {Object} event.originalEvent * The original event object. + * @param {Boolean} [event.preventDefaultAction=false] + * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). * @param {Object} event.userData * Arbitrary user-defined object. */ keyHandler: function () { }, /** - * Implement or assign implmentation to these handlers during or after + * Implement or assign implementation to these handlers during or after * calling the constructor. * @function * @param {Object} event @@ -416,13 +461,15 @@ * A reference to the tracker instance. * @param {Object} event.originalEvent * The original event object. + * @param {Boolean} [event.preventDefaultAction=false] + * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). * @param {Object} event.userData * Arbitrary user-defined object. */ focusHandler: function () { }, /** - * Implement or assign implmentation to these handlers during or after + * Implement or assign implementation to these handlers during or after * calling the constructor. * @function * @param {Object} event @@ -430,12 +477,22 @@ * A reference to the tracker instance. * @param {Object} event.originalEvent * The original event object. + * @param {Boolean} [event.preventDefaultAction=false] + * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). * @param {Object} event.userData * Arbitrary user-defined object. */ blurHandler: function () { } }; + /** + * Detect available mouse wheel event. + */ + $.MouseTracker.wheelEventName = ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version > 8 ) || + ( 'onwheel' in document.createElement( 'div' ) ) ? 'wheel' : // Modern browsers support 'wheel' + document.onmousewheel !== undefined ? 'mousewheel' : // Webkit and IE support at least 'mousewheel' + 'DOMMouseScroll'; // Assume old Firefox + /** * Starts tracking mouse events on this element. * @private @@ -445,7 +502,7 @@ var events = [ "mouseover", "mouseout", "mousedown", "mouseup", "mousemove", "click", - "DOMMouseScroll", "mousewheel", + $.MouseTracker.wheelEventName, "touchstart", "touchmove", "touchend", "keypress", "focus", "blur" @@ -454,6 +511,11 @@ event, i; + // Add 'MozMousePixelScroll' event handler for older Firefox + if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) { + events.push( "MozMousePixelScroll" ); + } + if ( !delegate.tracking ) { for ( i = 0; i < events.length; i++ ) { event = events[ i ]; @@ -478,7 +540,7 @@ var events = [ "mouseover", "mouseout", "mousedown", "mouseup", "mousemove", "click", - "DOMMouseScroll", "mousewheel", + $.MouseTracker.wheelEventName, "touchstart", "touchmove", "touchend", "keypress", "focus", "blur" @@ -487,6 +549,11 @@ event, i; + // Remove 'MozMousePixelScroll' event handler for older Firefox + if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) { + events.push( "MozMousePixelScroll" ); + } + if ( delegate.tracking ) { for ( i = 0; i < events.length; i++ ) { event = events[ i ]; @@ -630,9 +697,10 @@ if ( tracker.focusHandler ) { propagate = tracker.focusHandler( { - eventSource: tracker, - originalEvent: event, - userData: tracker.userData + eventSource: tracker, + originalEvent: event, + preventDefaultAction: false, + userData: tracker.userData } ); if ( propagate === false ) { @@ -652,9 +720,10 @@ if ( tracker.blurHandler ) { propagate = tracker.blurHandler( { - eventSource: tracker, - originalEvent: event, - userData: tracker.userData + eventSource: tracker, + originalEvent: event, + preventDefaultAction: false, + userData: tracker.userData } ); if ( propagate === false ) { @@ -674,12 +743,13 @@ if ( tracker.keyHandler ) { propagate = tracker.keyHandler( { - eventSource: tracker, - position: getMouseRelative( event, tracker.element ), - keyCode: event.keyCode ? event.keyCode : event.charCode, - shift: event.shiftKey, - originalEvent: event, - userData: tracker.userData + eventSource: tracker, + position: getMouseRelative( event, tracker.element ), + keyCode: event.keyCode ? event.keyCode : event.charCode, + shift: event.shiftKey, + originalEvent: event, + preventDefaultAction: false, + userData: tracker.userData } ); if ( !propagate ) { @@ -729,13 +799,14 @@ if ( tracker.enterHandler ) { propagate = tracker.enterHandler( { - eventSource: tracker, - position: getMouseRelative( isTouch ? event.changedTouches[ 0 ] : event, tracker.element ), + eventSource: tracker, + position: getMouseRelative( isTouch ? event.changedTouches[ 0 ] : event, tracker.element ), insideElementPressed: delegate.insideElementPressed, - buttonDownAny: IS_BUTTON_DOWN, - isTouchEvent: isTouch, - originalEvent: event, - userData: tracker.userData + buttonDownAny: IS_BUTTON_DOWN, + isTouchEvent: isTouch, + originalEvent: event, + preventDefaultAction: false, + userData: tracker.userData } ); if ( propagate === false ) { @@ -785,13 +856,14 @@ if ( tracker.exitHandler ) { propagate = tracker.exitHandler( { - eventSource: tracker, - position: getMouseRelative( isTouch ? event.changedTouches[ 0 ] : event, tracker.element ), + eventSource: tracker, + position: getMouseRelative( isTouch ? event.changedTouches[ 0 ] : event, tracker.element ), insideElementPressed: delegate.insideElementPressed, - buttonDownAny: IS_BUTTON_DOWN, - isTouchEvent: isTouch, - originalEvent: event, - userData: tracker.userData + buttonDownAny: IS_BUTTON_DOWN, + isTouchEvent: isTouch, + originalEvent: event, + preventDefaultAction: false, + userData: tracker.userData } ); @@ -829,11 +901,12 @@ if ( tracker.pressHandler ) { propagate = tracker.pressHandler( { - eventSource: tracker, - position: getMouseRelative( eventOrTouchPoint, tracker.element ), - isTouchEvent: isTouch, - originalEvent: event, - userData: tracker.userData + eventSource: tracker, + position: getMouseRelative( eventOrTouchPoint, tracker.element ), + isTouchEvent: isTouch, + originalEvent: event, + preventDefaultAction: false, + userData: tracker.userData } ); if ( propagate === false ) { @@ -924,13 +997,14 @@ if ( tracker.releaseHandler ) { propagate = tracker.releaseHandler( { - eventSource: tracker, - position: getMouseRelative( isTouch ? event.changedTouches[ 0 ] : event, tracker.element ), - insideElementPressed: insideElementPressed, + eventSource: tracker, + position: getMouseRelative( isTouch ? event.changedTouches[ 0 ] : event, tracker.element ), + insideElementPressed: insideElementPressed, insideElementReleased: insideElementReleased, - isTouchEvent: isTouch, - originalEvent: event, - userData: tracker.userData + isTouchEvent: isTouch, + originalEvent: event, + preventDefaultAction: false, + userData: tracker.userData } ); if ( propagate === false ) { @@ -1042,19 +1116,42 @@ var propagate = tracker.moveHandler( { - eventSource: tracker, - position: getMouseRelative( event, tracker.element ), - isTouchEvent: false, - originalEvent: event, - userData: tracker.userData + eventSource: tracker, + position: getMouseRelative( event, tracker.element ), + isTouchEvent: false, + originalEvent: event, + preventDefaultAction: false, + userData: tracker.userData } ); if ( propagate === false ) { $.cancelEvent( event ); } } + if ( tracker.stopHandler ) { + clearTimeout( tracker.stopTimeOut ); + tracker.stopTimeOut = setTimeout( function() { + onMouseStop( tracker, event ); + }, tracker.stopDelay ); + } + } + + /** + * @private + * @inner + */ + function onMouseStop( tracker, originalMoveEvent ) { + if ( tracker.stopHandler ) { + tracker.stopHandler( { + eventSource: tracker, + position: getMouseRelative( originalMoveEvent, tracker.element ), + isTouchEvent: false, + originalEvent: originalMoveEvent, + preventDefaultAction: false, + userData: tracker.userData + } ); + } } - /** * @private @@ -1068,49 +1165,86 @@ /** + * Handler for 'wheel' events + * * @private * @inner */ - function onMouseWheelSpin( tracker, event, isTouch ) { + function onWheel( tracker, event ) { + handleWheelEvent( tracker, event, event, false ); + } + + + /** + * Handler for 'mousewheel', 'DOMMouseScroll', and 'MozMousePixelScroll' events + * + * @private + * @inner + */ + function onMouseWheel( tracker, event ) { + // For legacy IE, access the global (window) event object + event = event || window.event; + + // Simulate a 'wheel' event + var simulatedEvent = { + target: event.target || event.srcElement, + type: "wheel", + shiftKey: event.shiftKey || false, + clientX: event.clientX, + clientY: event.clientY, + pageX: event.pageX ? event.pageX : event.clientX, + pageY: event.pageY ? event.pageY : event.clientY, + deltaMode: event.type == "MozMousePixelScroll" ? 0 : 1, // 0=pixel, 1=line, 2=page + deltaX: 0, + deltaZ: 0 + }; + + // Calculate deltaY + if ( $.MouseTracker.wheelEventName == "mousewheel" ) { + simulatedEvent.deltaY = - 1 / $.DEFAULT_SETTINGS.pixelsPerWheelLine * event.wheelDelta; + } else { + simulatedEvent.deltaY = event.detail; + } + + handleWheelEvent( tracker, simulatedEvent, event, false ); + } + + + /** + * Handles 'wheel' events. + * The event may be simulated by the legacy mouse wheel event handler (onMouseWheel()) or onTouchMove(). + * + * @private + * @inner + */ + function handleWheelEvent( tracker, event, originalEvent, isTouch ) { var nDelta = 0, propagate; isTouch = isTouch || false; - if ( !event ) { // For IE, access the global (window) event object - event = window.event; - } - - if ( event.wheelDelta ) { // IE and Opera - nDelta = event.wheelDelta; - if ( window.opera ) { // Opera has the values reversed - nDelta = -nDelta; - } - } else if ( event.detail ) { // Mozilla FireFox - nDelta = -event.detail; - } - //The nDelta variable is gated to provide smooth z-index scrolling - //since the mouse wheel allows for substantial deltas meant for rapid - //y-index scrolling. - nDelta = nDelta > 0 ? 1 : -1; + // The nDelta variable is gated to provide smooth z-index scrolling + // since the mouse wheel allows for substantial deltas meant for rapid + // y-index scrolling. + // event.deltaMode: 0=pixel, 1=line, 2=page + // TODO: Deltas in pixel mode should be accumulated then a scroll value computed after $.DEFAULT_SETTINGS.pixelsPerWheelLine threshold reached + nDelta = event.deltaY < 0 ? 1 : -1; if ( tracker.scrollHandler ) { propagate = tracker.scrollHandler( { - eventSource: tracker, - // Note: Ok to call getMouseRelative on passed event for isTouch==true since - // event.pageX/event.pageY are added to the original touchmove event in - // onTouchMove(). - position: getMouseRelative( event, tracker.element ), - scroll: nDelta, - shift: event.shiftKey, - isTouchEvent: isTouch, - originalEvent: event, - userData: tracker.userData + eventSource: tracker, + position: getMouseRelative( event, tracker.element ), + scroll: nDelta, + shift: event.shiftKey, + isTouchEvent: isTouch, + originalEvent: originalEvent, + preventDefaultAction: false, + userData: tracker.userData } ); if ( propagate === false ) { - $.cancelEvent( event ); + $.cancelEvent( originalEvent ); } } } @@ -1143,13 +1277,14 @@ if ( tracker.clickHandler ) { propagate = tracker.clickHandler( { - eventSource: tracker, - position: getMouseRelative( eventOrTouchPoint, tracker.element ), - quick: quick, - shift: event.shiftKey, - isTouchEvent: isTouch, - originalEvent: event, - userData: tracker.userData + eventSource: tracker, + position: getMouseRelative( eventOrTouchPoint, tracker.element ), + quick: quick, + shift: event.shiftKey, + isTouchEvent: isTouch, + originalEvent: event, + preventDefaultAction: false, + userData: tracker.userData } ); if ( propagate === false ) { @@ -1181,13 +1316,14 @@ if ( tracker.dragHandler ) { propagate = tracker.dragHandler( { - eventSource: tracker, - position: getMouseRelative( eventOrTouchPoint, tracker.element ), - delta: delta, - shift: event.shiftKey, - isTouchEvent: isTouch, - originalEvent: event, - userData: tracker.userData + eventSource: tracker, + position: getMouseRelative( eventOrTouchPoint, tracker.element ), + delta: delta, + shift: event.shiftKey, + isTouchEvent: isTouch, + originalEvent: event, + preventDefaultAction: false, + userData: tracker.userData } ); if ( propagate === false ) { @@ -1229,15 +1365,22 @@ if ( Math.abs( THIS[ tracker.hash ].lastPinchDelta - pinchDelta ) > 75 ) { //$.console.debug( "pinch delta : " + pinchDelta + " | previous : " + THIS[ tracker.hash ].lastPinchDelta); - // Simulate a mouse wheel scroll event + // Simulate a 'wheel' event var simulatedEvent = { - shiftKey: event.shiftKey || false, - pageX: THIS[ tracker.hash ].pinchMidpoint.x, - pageY: THIS[ tracker.hash ].pinchMidpoint.y, - detail: ( THIS[ tracker.hash ].lastPinchDelta > pinchDelta ) ? 1 : -1 + target: event.target || event.srcElement, + type: "wheel", + shiftKey: event.shiftKey || false, + clientX: THIS[ tracker.hash ].pinchMidpoint.x, + clientY: THIS[ tracker.hash ].pinchMidpoint.y, + pageX: THIS[ tracker.hash ].pinchMidpoint.x, + pageY: THIS[ tracker.hash ].pinchMidpoint.y, + deltaMode: 1, // 0=pixel, 1=line, 2=page + deltaX: 0, + deltaY: ( THIS[ tracker.hash ].lastPinchDelta > pinchDelta ) ? 1 : -1, + deltaZ: 0 }; - onMouseWheelSpin( tracker, simulatedEvent, true ); + handleWheelEvent( tracker, simulatedEvent, event, true ); THIS[ tracker.hash ].lastPinchDelta = pinchDelta; } diff --git a/src/openseadragon.js b/src/openseadragon.js index 8d12d897..421ae73e 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -226,6 +226,9 @@ * @param {Number} [options.maxImageCacheCount=100] * The max number of images we should keep in memory (per drawer). * + * @param {Boolean} [options.useCanvas=true] + * Set to false to not use an HTML canvas element for image rendering even if canvas is supported. + * * @param {Number} [options.minPixelRatio=0.5] * The higher the minPixelRatio, the lower the quality of the image that * is considered sufficient to stop rendering a given zoom level. For @@ -293,6 +296,12 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ toString = Object.prototype.toString, hasOwn = Object.prototype.hasOwnProperty; + // Detects canvas support + function isCanvasSupported() { + var canvasElement = document.createElement( 'canvas' ); + return !!( $.isFunction( canvasElement.getContext ) && + canvasElement.getContext( '2d' ) ); + } /** * Taken from jQuery 1.6.1 @@ -386,6 +395,52 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ }; + /** + * True if the browser supports the HTML5 canvas element + * @name $.supportsCanvas + * @property + */ + $.supportsCanvas = isCanvasSupported(); + + + /** + * Detect event model and create appropriate _addEvent/_removeEvent methods + */ + if ( window.addEventListener ) { + $._addEvent = function ( element, eventName, handler, useCapture ) { + element = $.getElement( element ); + element.addEventListener( eventName, handler, useCapture ); + }; + } else if ( window.attachEvent ) { + $._addEvent = function ( element, eventName, handler, useCapture ) { + element = $.getElement( element ); + element.attachEvent( 'on' + eventName, handler ); + if ( useCapture && element.setCapture ) { + element.setCapture(); + } + }; + } else { + throw new Error( "No known event model." ); + } + + if ( window.removeEventListener ) { + $._removeEvent = function ( element, eventName, handler, useCapture ) { + element = $.getElement( element ); + element.removeEventListener( eventName, handler, useCapture ); + }; + } else if ( window.detachEvent ) { + $._removeEvent = function( element, eventName, handler, useCapture ) { + element = $.getElement( element ); + element.detachEvent( 'on' + eventName, handler ); + if ( useCapture && element.releaseCapture ) { + element.releaseCapture(); + } + }; + } else { + throw new Error( "No known event model." ); + } + + }( OpenSeadragon )); /** @@ -517,6 +572,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ immediateRender: false, minZoomImageRatio: 0.9, //-> closer to 0 allows zoom out to infinity maxZoomPixelRatio: 1.1, //-> higher allows 'over zoom' into pixels + pixelsPerWheelLine: 40, //DEFAULT CONTROL SETTINGS showSequenceControl: true, //SEQUENCE @@ -559,6 +615,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ imageLoaderLimit: 0, maxImageCacheCount: 200, timeout: 30000, + useCanvas: true, // Use canvas element for drawing if available //INTERFACE RESOURCE SETTINGS prefixUrl: "/images/", @@ -1192,33 +1249,9 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ * @param {String} eventName * @param {Function} handler * @param {Boolean} [useCapture] - * @throws {Error} */ addEvent: function( element, eventName, handler, useCapture ) { - element = $.getElement( element ); - - //TODO: Why do this if/else on every method call instead of just - // defining this function once based on the same logic - if ( element.addEventListener ) { - $.addEvent = function( element, eventName, handler, useCapture ){ - element = $.getElement( element ); - element.addEventListener( eventName, handler, useCapture ); - }; - } else if ( element.attachEvent ) { - $.addEvent = function( element, eventName, handler, useCapture ){ - element = $.getElement( element ); - element.attachEvent( "on" + eventName, handler ); - if ( useCapture && element.setCapture ) { - element.setCapture(); - } - }; - } else { - throw new Error( - "Unable to attach event handler, no known technique." - ); - } - - return $.addEvent( element, eventName, handler, useCapture ); + return $._addEvent( element, eventName, handler, useCapture ); }, @@ -1231,32 +1264,9 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ * @param {String} eventName * @param {Function} handler * @param {Boolean} [useCapture] - * @throws {Error} */ removeEvent: function( element, eventName, handler, useCapture ) { - element = $.getElement( element ); - - //TODO: Why do this if/else on every method call instead of just - // defining this function once based on the same logic - if ( element.removeEventListener ) { - $.removeEvent = function( element, eventName, handler, useCapture ) { - element = $.getElement( element ); - element.removeEventListener( eventName, handler, useCapture ); - }; - } else if ( element.detachEvent ) { - $.removeEvent = function( element, eventName, handler, useCapture ) { - element = $.getElement( element ); - element.detachEvent("on" + eventName, handler); - if ( useCapture && element.releaseCapture ) { - element.releaseCapture(); - } - }; - } else { - throw new Error( - "Unable to detach event handler, no known technique." - ); - } - return $.removeEvent( element, eventName, handler, useCapture ); + return $._removeEvent( element, eventName, handler, useCapture ); }, diff --git a/src/tile.js b/src/tile.js index 3ba62636..fc985d1e 100644 --- a/src/tile.js +++ b/src/tile.js @@ -54,8 +54,9 @@ * this tile failed to load? * @property {String} url The URL of this tile's image. * @property {Boolean} loaded Is this tile loaded? - * @property {Boolean} loading Is this tile loading - * @property {Element} element The HTML element for this tile + * @property {Boolean} loading Is this tile loading? + * @property {Element} element The HTML div element for this tile + * @property {Element} imgElement The HTML img element for this tile * @property {Image} image The Image object for this tile * @property {String} style The alias of this.element.style. * @property {String} position This tile's position on screen, in pixels. @@ -78,6 +79,7 @@ $.Tile = function(level, x, y, bounds, exists, url) { this.loading = false; this.element = null; + this.imgElement = null; this.image = null; this.style = null; @@ -122,9 +124,12 @@ $.Tile.prototype = { // content during animation of the container size. if ( !this.element ) { - this.element = $.makeNeutralElement("img"); - this.element.src = this.url; - this.element.style.msInterpolationMode = "nearest-neighbor"; + this.element = $.makeNeutralElement( "div" ); + this.imgElement = $.makeNeutralElement( "img" ); + this.imgElement.src = this.url; + this.imgElement.style.msInterpolationMode = "nearest-neighbor"; + this.imgElement.style.width = "100%"; + this.imgElement.style.height = "100%"; this.style = this.element.style; this.style.position = "absolute"; @@ -132,6 +137,9 @@ $.Tile.prototype = { if ( this.element.parentNode != container ) { container.appendChild( this.element ); } + if ( this.imgElement.parentNode != this.element ) { + this.element.appendChild( this.imgElement ); + } this.style.top = this.position.y + "px"; this.style.left = this.position.x + "px"; @@ -216,6 +224,9 @@ $.Tile.prototype = { * @function */ unload: function() { + if ( this.imgElement && this.imgElement.parentNode ) { + this.imgElement.parentNode.removeChild( this.imgElement ); + } if ( this.element && this.element.parentNode ) { this.element.parentNode.removeChild( this.element ); } @@ -223,10 +234,11 @@ $.Tile.prototype = { delete TILE_CACHE[ this.url ]; } - this.element = null; - this.image = null; - this.loaded = false; - this.loading = false; + this.element = null; + this.imgElement = null; + this.image = null; + this.loaded = false; + this.loading = false; } }; diff --git a/src/tilesource.js b/src/tilesource.js index 0120e515..7448ba42 100644 --- a/src/tilesource.js +++ b/src/tilesource.js @@ -292,6 +292,9 @@ $.TileSource.prototype = { } callback = function( data ){ + if( typeof(data) === "string" ) { + data = $.parseXml( data ); + } var $TileSource = $.TileSource.determineType( _this, data, url ); if ( !$TileSource ) { _this.raiseEvent( 'open-failed', { message: "Unable to load TileSource", source: url } ); diff --git a/src/viewer.js b/src/viewer.js index 11c1cbfa..9e608f28 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -100,7 +100,7 @@ $.Viewer = function( options ) { //internal state and dom identifiers id: options.id, - hash: options.id, + hash: options.hash || options.id, //dom nodes element: null, @@ -147,6 +147,15 @@ $.Viewer = function( options ) { }, $.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 ), @@ -267,58 +276,62 @@ $.Viewer = function( options ) { this.keyboardCommandArea.innerTracker = new $.MouseTracker({ _this : this, element: this.keyboardCommandArea, - focusHandler: function(){ - var point = $.getElementPosition( this.element ); - window.scrollTo( 0, point.y ); + focusHandler: function( event ){ + if ( !event.preventDefaultAction ) { + var point = $.getElementPosition( this.element ); + window.scrollTo( 0, point.y ); + } }, keyHandler: function( event ){ - switch( event.keyCode ){ - case 61://=|+ - _this.viewport.zoomBy(1.1); - _this.viewport.applyConstraints(); - return false; - case 45://-|_ - _this.viewport.zoomBy(0.9); - _this.viewport.applyConstraints(); - return false; - case 48://0|) - _this.viewport.goHome(); - _this.viewport.applyConstraints(); - return false; - case 119://w - case 87://W - case 38://up arrow - if ( event.shift ) { + if ( !event.preventDefaultAction ) { + switch( event.keyCode ){ + case 61://=|+ _this.viewport.zoomBy(1.1); - } else { - _this.viewport.panBy(new $.Point(0, -0.05)); - } - _this.viewport.applyConstraints(); - return false; - case 115://s - case 83://S - case 40://down arrow - if ( event.shift ) { + _this.viewport.applyConstraints(); + return false; + case 45://-|_ _this.viewport.zoomBy(0.9); - } else { - _this.viewport.panBy(new $.Point(0, 0.05)); - } - _this.viewport.applyConstraints(); - return false; - case 97://a - case 37://left arrow - _this.viewport.panBy(new $.Point(-0.05, 0)); - _this.viewport.applyConstraints(); - return false; - case 100://d - case 39://right arrow - _this.viewport.panBy(new $.Point(0.05, 0)); - _this.viewport.applyConstraints(); - return false; - default: - //console.log( 'navigator keycode %s', event.keyCode ); - return true; + _this.viewport.applyConstraints(); + return false; + case 48://0|) + _this.viewport.goHome(); + _this.viewport.applyConstraints(); + return false; + case 119://w + case 87://W + case 38://up arrow + if ( event.shift ) { + _this.viewport.zoomBy(1.1); + } else { + _this.viewport.panBy(new $.Point(0, -0.05)); + } + _this.viewport.applyConstraints(); + return false; + case 115://s + case 83://S + case 40://down arrow + if ( event.shift ) { + _this.viewport.zoomBy(0.9); + } else { + _this.viewport.panBy(new $.Point(0, 0.05)); + } + _this.viewport.applyConstraints(); + return false; + case 97://a + case 37://left arrow + _this.viewport.panBy(new $.Point(-0.05, 0)); + _this.viewport.applyConstraints(); + return false; + case 100://d + case 39://right arrow + _this.viewport.panBy(new $.Point(0.05, 0)); + _this.viewport.applyConstraints(); + return false; + default: + //console.log( 'navigator keycode %s', event.keyCode ); + return true; + } } } }).setTracking( true ); // default state @@ -521,7 +534,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, VIEWERS[ this.hash ] = null; delete VIEWERS[ this.hash ]; - this.raiseEvent( 'close', {} ); + this.raiseEvent( 'close' ); return this; }, @@ -1452,7 +1465,7 @@ function onBlur(){ function onCanvasClick( event ) { var zoomPerClick, factor; - if ( this.viewport && event.quick ) { // ignore clicks where mouse moved + if ( !event.preventDefaultAction && this.viewport && event.quick ) { // ignore clicks where mouse moved zoomPerClick = this.zoomPerClick; factor = event.shift ? 1.0 / zoomPerClick : zoomPerClick; this.viewport.zoomBy( @@ -1465,12 +1478,13 @@ function onCanvasClick( event ) { tracker: event.eventSource, position: event.position, quick: event.quick, - shift: event.shift + shift: event.shift, + originalEvent: event.originalEvent }); } function onCanvasDrag( event ) { - if ( this.viewport ) { + if ( !event.preventDefaultAction && this.viewport ) { if( !this.panHorizontal ){ event.delta.x = 0; } @@ -1490,7 +1504,8 @@ function onCanvasDrag( event ) { tracker: event.eventSource, position: event.position, delta: event.delta, - shift: event.shift + shift: event.shift, + originalEvent: event.originalEvent }); } @@ -1502,13 +1517,14 @@ function onCanvasRelease( event ) { tracker: event.eventSource, position: event.position, insideElementPressed: event.insideElementPressed, - insideElementReleased: event.insideElementReleased + insideElementReleased: event.insideElementReleased, + originalEvent: event.originalEvent }); } function onCanvasScroll( event ) { var factor; - if ( this.viewport ) { + if ( !event.preventDefaultAction && this.viewport ) { factor = Math.pow( this.zoomPerScroll, event.scroll ); this.viewport.zoomBy( factor, @@ -1520,7 +1536,8 @@ function onCanvasScroll( event ) { tracker: event.eventSource, position: event.position, scroll: event.scroll, - shift: event.shift + shift: event.shift, + originalEvent: event.originalEvent }); //cancels event return false; @@ -1537,7 +1554,8 @@ function onContainerExit( event ) { tracker: event.eventSource, position: event.position, insideElementPressed: event.insideElementPressed, - buttonDownAny: event.buttonDownAny + buttonDownAny: event.buttonDownAny, + originalEvent: event.originalEvent }); } @@ -1552,7 +1570,8 @@ function onContainerRelease( event ) { tracker: event.eventSource, position: event.position, insideElementPressed: event.insideElementPressed, - insideElementReleased: event.insideElementReleased + insideElementReleased: event.insideElementReleased, + originalEvent: event.originalEvent }); } @@ -1563,7 +1582,8 @@ function onContainerEnter( event ) { tracker: event.eventSource, position: event.position, insideElementPressed: event.insideElementPressed, - buttonDownAny: event.buttonDownAny + buttonDownAny: event.buttonDownAny, + originalEvent: event.originalEvent }); } diff --git a/src/viewport.js b/src/viewport.js index 6fa8931f..12fbd956 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -916,19 +916,41 @@ $.Viewport.prototype = { return viewerCoordinates.plus( OpenSeadragon.getElementPosition( this.viewer.element )); }, - + /** - * Get the zoom ratio of the image. 1 means original image size, 0.5 half size... + * Convert a viewport zoom to an image zoom. + * Image zoom: ratio of the original image size to displayed image size. + * 1 means original image size, 0.5 half size... + * Viewport zoom: ratio of the displayed image's width to viewport's width. + * 1 means identical width, 2 means image's width is twice the viewport's width... * @function - * @param {Boolean} current If true gives the current zoom otherwise gives the + * @param {Number} viewportZoom The viewport zoom * target zoom. - * @returns {Number} + * @returns {Number} imageZoom The image zoom */ - getImageZoomRatio: function( current ) { + viewportToImageZoom: function( viewportZoom ) { var imageWidth = this.viewer.source.dimensions.x; var containerWidth = this.getContainerSize().x; - var zoomToZoomLevelRatio = containerWidth / imageWidth; - return this.getZoom( current ) * zoomToZoomLevelRatio; + var viewportToImageZoomRatio = containerWidth / imageWidth; + return viewportZoom * viewportToImageZoomRatio; + }, + + /** + * Convert an image zoom to a viewport zoom. + * Image zoom: ratio of the original image size to displayed image size. + * 1 means original image size, 0.5 half size... + * Viewport zoom: ratio of the displayed image's width to viewport's width. + * 1 means identical width, 2 means image's width is twice the viewport's width... + * @function + * @param {Number} imageZoom The image zoom + * target zoom. + * @returns {Number} viewportZoom The viewport zoom + */ + imageToViewportZoom: function( imageZoom ) { + var imageWidth = this.viewer.source.dimensions.x; + var containerWidth = this.getContainerSize().x; + var viewportToImageZoomRatio = imageWidth / containerWidth; + return imageZoom * viewportToImageZoomRatio; } }; diff --git a/test/events.js b/test/events.js index 9c8fefbb..6fb7f23c 100644 --- a/test/events.js +++ b/test/events.js @@ -74,6 +74,8 @@ eventsHandledMouseTracker = 0, eventSourcePassedMouseTracker = 0, originalEventsPassedMouseTracker = 0, + eventsHandledViewer = 0, + originalEventsPassedViewer = 0, releasesExpected = 1, clicksExpected = 1; @@ -117,15 +119,27 @@ $canvas.simulate( 'blur', event ); }; + var checkOriginalEventReceivedViewer = function ( event ) { + eventsHandledViewer++; + //TODO Provide a better check for the original event...simulate doesn't currently extend the object + // with arbitrary user data. + if ( event && event.originalEvent ) { + originalEventsPassedViewer++; + } + }; + var onEventSourceDrag = function ( event ) { + checkOriginalEventReceivedViewer( event ); dragsHandledEventSource++; }; var onEventSourceRelease = function ( event ) { + checkOriginalEventReceivedViewer( event ); releasesHandledEventSource++; }; var onEventSourceClick = function ( event ) { + checkOriginalEventReceivedViewer( event ); clicksHandledEventSource++; }; @@ -184,6 +198,7 @@ equal( dragsHandledEventSource, dragCount, "'canvas-drag' event count matches 'mousemove' event count (" + dragCount + ")" ); equal( releasesHandledEventSource, releasesExpected, "'canvas-release' event count matches expected (" + releasesExpected + ")" ); equal( clicksHandledEventSource, releasesExpected, "'canvas-click' event count matches expected (" + releasesExpected + ")" ); + equal( originalEventsPassedViewer, eventsHandledViewer, "Original event received count matches expected (" + eventsHandledViewer + ")" ); equal( eventSourcePassedMouseTracker, eventsHandledMouseTracker, "Event source received count matches expected (" + eventsHandledMouseTracker + ")" ); equal( originalEventsPassedMouseTracker, eventsHandledMouseTracker, "Original event received count matches expected (" + eventsHandledMouseTracker + ")" ); @@ -197,4 +212,72 @@ viewer.open( '/test/data/testpattern.dzi' ); } ); + // ---------- + asyncTest( 'MouseTracker preventDefaultAction', function () { + var $canvas = $( viewer.element ).find( '.openseadragon-canvas' ).not( '.navigator .openseadragon-canvas' ), + tracker = viewer.innerTracker, + origClickHandler, + origDragHandler, + dragCount = 10, + originalZoom = 0, + originalBounds = null; + + var onOpen = function ( event ) { + viewer.removeHandler( 'open', onOpen ); + + // Hook viewer events to set preventDefaultAction + origClickHandler = tracker.clickHandler; + tracker.clickHandler = function ( event ) { + event.preventDefaultAction = true; + return origClickHandler( event ); + }; + origDragHandler = tracker.dragHandler; + tracker.dragHandler = function ( event ) { + event.preventDefaultAction = true; + return origDragHandler( event ); + }; + + originalZoom = viewer.viewport.getZoom(); + originalBounds = viewer.viewport.getBounds(); + + var event = { + clientX:1, + clientY:1 + }; + + $canvas.simulate( 'focus', event ); + // Drag to pan + Util.simulateViewerClickWithDrag( { + viewer: viewer, + widthFactor: 0.25, + heightFactor: 0.25, + dragCount: dragCount, + dragDx: 1, + dragDy: 1 + } ); + // Click to zoom + Util.simulateViewerClickWithDrag( { + viewer: viewer, + widthFactor: 0.25, + heightFactor: 0.25, + dragCount: 0, + dragDx: 0, + dragDy: 0 + } ); + $canvas.simulate( 'blur', event ); + + var zoom = viewer.viewport.getZoom(), + bounds = viewer.viewport.getBounds(); + + equal( zoom, originalZoom, "Zoom prevented" ); + ok( bounds.x == originalBounds.x && bounds.y == originalBounds.y, 'Pan prevented' ); + + viewer.close(); + start(); + }; + + viewer.addHandler( 'open', onOpen ); + viewer.open( '/test/data/testpattern.dzi' ); + } ); + } )(); diff --git a/test/units.js b/test/units.js index 1417b801..348835b3 100644 --- a/test/units.js +++ b/test/units.js @@ -91,9 +91,14 @@ function checkZoom() { var currentImageWidth = getCurrentImageWidth(); - var expected = currentImageWidth / imageWidth; - var actual = viewport.getImageZoomRatio(true); - equal(actual, expected); + var expectedImageZoom = currentImageWidth / imageWidth; + var expectedViewportZoom = viewport.getZoom(true); + var actualImageZoom = viewport.viewportToImageZoom( + expectedViewportZoom); + equal(actualImageZoom, expectedImageZoom); + + var actualViewportZoom = viewport.imageToViewportZoom(actualImageZoom); + equal(actualViewportZoom, expectedViewportZoom); } checkZoom();