diff --git a/changelog.txt b/changelog.txt index e77bb84d..a68ba727 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,7 +1,35 @@ OPENSEADRAGON CHANGELOG ======================= -1.2.0: (in progress) +1.2.2: (in progress) + +1.2.1: + +* Added preserveOverlays option (#561) +* Fixed: DZI tilesource was broken on IE8/IE9 (#563) +* Exposed secondary pointer button (middle, right, etc.) events from MouseTracker and through viewer (#479) +* MouseTracker - Improved IE 8 compatibility (#562) +* MouseTracker - Improved IE 9+ compatibility (#564) +* MouseTracker - Simulated touchenter/touchleave events now bubble to parent element MouseTrackers (#566) +* MouseTracker - Improved multitouch support in enter/exit event handlers (#566) +* MouseTracker - orphaned tracked touch pointers removed (fix for #539) +* MouseTracker - removed touchenter/touchleave event support since the events don't exist on any known platform and have been removed from the W3C specification (#566) +* Removed Viewer onContainerPress/onContainerRelease handlers (and the associated 'container-release' event ) that were never fired due to the canvas (child) element capturing the DOM events (#566) +* Added 'canvas-enter', 'canvas-exit', and 'canvas-press' events to Viewer (#566) +* ButtonGroup - removed obsolete MouseTracker event handlers (#566) +* MouseTracker - added keydown and keyup handlers (#568) +* Modifier keys ignored in keyboard navigation handlers (#503) +* Requesting keyboard focus when viewer is clicked (#537) +* Arrow key navigation fixed across platforms (#565) +* Removed textarea element from viewer DOM. Viewer.canvas now handles keyboard navigation (#569) +* Removed 'position' property from MouseTracker keyDownHandler/keyUpHandler/keyHandler functions (#573) +* Fixed pointer event model detection for IE 10 and IE 11 (#571) +* Added setMouseNavEnabled() support to Navigator (#572) +* MouseTracker now defaults to tracking on (#558) +* Removed Viewer focusHandler/onCanvasFocus (#577) +* Added tabIndex option to viewer (#577) + +1.2.0: * New combined IIIF TileSource for 1.0 through 2.0 (#441) * BREAKING CHANGE: Removed IIIF1_1TileSource (now that IIIFTileSource supports all versions) @@ -18,8 +46,9 @@ OPENSEADRAGON CHANGELOG * Viewport.setRotation now allows all rotation angles (#466) * Pinch rotate is now available (defaults to off) (#468) * Added option for home button to fill viewer (#474) -* Now handling iframe/frame mouseouts properly (#481) * Better handling of mid-update image loaded callbacks (#409) +* Tracked pointers are now cleaned up when Viewer.setMouseNavEnabled(false) is called (#518) +* Added explicit pointer capture for touch event model touchstart events (#552) 1.1.1: diff --git a/package.json b/package.json index 486d8743..992fd364 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "OpenSeadragon", - "version": "1.1.1", + "version": "1.2.1", "description": "Provides a smooth, zoomable user interface for HTML/Javascript.", "devDependencies": { "grunt": "^0.4.5", diff --git a/src/button.js b/src/button.js index 104955e0..42f68afc 100644 --- a/src/button.js +++ b/src/button.js @@ -355,7 +355,7 @@ $.Button = function( options ) { return true; } - }).setTracking( true ); + }); outTo( this, $.ButtonState.REST ); }; diff --git a/src/buttongroup.js b/src/buttongroup.js index 2837805a..49cc9c2e 100644 --- a/src/buttongroup.js +++ b/src/buttongroup.js @@ -105,23 +105,7 @@ $.ButtonGroup = function( options ) { } } }, - pressHandler: function ( event ) { - if ( event.pointerType === 'touch' && !$.MouseTracker.haveTouchEnter ) { - var i; - for ( i = 0; i < _this.buttons.length; i++ ) { - _this.buttons[ i ].notifyGroupEnter(); - } - } - }, - releaseHandler: function ( event ) { - var i; - if ( !event.insideElementReleased || ( event.pointerType === 'touch' && !$.MouseTracker.haveTouchEnter ) ) { - for ( i = 0; i < _this.buttons.length; i++ ) { - _this.buttons[ i ].notifyGroupExit(); - } - } - } - }).setTracking( true ); + }); }; $.ButtonGroup.prototype = /** @lends OpenSeadragon.ButtonGroup.prototype */{ diff --git a/src/dzitilesource.js b/src/dzitilesource.js index eb283fab..99dd92d1 100644 --- a/src/dzitilesource.js +++ b/src/dzitilesource.js @@ -107,8 +107,10 @@ $.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead var ns; if ( data.Image ) { ns = data.Image.xmlns; - } else if ( data.documentElement && "Image" == data.documentElement.localName ) { - ns = data.documentElement.namespaceURI; + } else if ( data.documentElement) { + if ("Image" == data.documentElement.localName || "Image" == data.documentElement.tagName) { + ns = data.documentElement.namespaceURI; + } } return ( "http://schemas.microsoft.com/deepzoom/2008" == ns || @@ -221,7 +223,7 @@ function configureFromXML( tileSource, xmlDoc ){ } var root = xmlDoc.documentElement, - rootName = root.localName, + rootName = root.localName || root.tagName, ns = xmlDoc.documentElement.namespaceURI, configuration = null, displayRects = [], @@ -234,7 +236,10 @@ function configureFromXML( tileSource, xmlDoc ){ if ( rootName == "Image" ) { try { - sizeNode = root.getElementsByTagNameNS(ns, "Size" )[ 0 ]; + sizeNode = root.getElementsByTagName("Size" )[ 0 ]; + if (sizeNode === undefined) { + sizeNode = root.getElementsByTagNameNS(ns, "Size" )[ 0 ]; + } configuration = { Image: { @@ -257,11 +262,17 @@ function configureFromXML( tileSource, xmlDoc ){ ); } - dispRectNodes = root.getElementsByTagNameNS(ns, "DisplayRect" ); + dispRectNodes = root.getElementsByTagName("DisplayRect" ); + if (dispRectNodes === undefined) { + dispRectNodes = root.getElementsByTagNameNS(ns, "DisplayRect" )[ 0 ]; + } for ( i = 0; i < dispRectNodes.length; i++ ) { dispRectNode = dispRectNodes[ i ]; - rectNode = dispRectNode.getElementsByTagNameNS(ns, "Rect" )[ 0 ]; + rectNode = dispRectNode.getElementsByTagName("Rect" )[ 0 ]; + if (rectNode === undefined) { + rectNode = dispRectNode.getElementsByTagNameNS(ns, "Rect" )[ 0 ]; + } displayRects.push({ Rect: { diff --git a/src/mousetracker.js b/src/mousetracker.js index 0c865c74..ec677fb7 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -34,7 +34,10 @@ (function ( $ ) { - // dictionary from hash to private properties + // All MouseTracker instances + var MOUSETRACKERS = []; + + // dictionary from hash to private properties var THIS = {}; @@ -51,6 +54,9 @@ * @param {Element|String} options.element * A reference to an element or an element id for which the pointer/key * events will be monitored. + * @param {Boolean} [options.startDisabled=false] + * If true, event tracking on the element will not start until + * {@link OpenSeadragon.MouseTracker.setTracking|setTracking} is called. * @param {Number} options.clickTimeThreshold * The number of milliseconds within which a pointer down-up event combination * will be treated as a click gesture. @@ -72,8 +78,12 @@ * An optional handler for pointer exit. * @param {OpenSeadragon.EventHandler} [options.pressHandler=null] * An optional handler for pointer press. + * @param {OpenSeadragon.EventHandler} [options.nonPrimaryPressHandler=null] + * An optional handler for pointer non-primary button press. * @param {OpenSeadragon.EventHandler} [options.releaseHandler=null] * An optional handler for pointer release. + * @param {OpenSeadragon.EventHandler} [options.nonPrimaryReleaseHandler=null] + * An optional handler for pointer non-primary button release. * @param {OpenSeadragon.EventHandler} [options.moveHandler=null] * An optional handler for pointer move. * @param {OpenSeadragon.EventHandler} [options.scrollHandler=null] @@ -88,6 +98,10 @@ * An optional handler for after a drag gesture. * @param {OpenSeadragon.EventHandler} [options.pinchHandler=null] * An optional handler for the pinch gesture. + * @param {OpenSeadragon.EventHandler} [options.keyDownHandler=null] + * An optional handler for keydown. + * @param {OpenSeadragon.EventHandler} [options.keyUpHandler=null] + * An optional handler for keyup. * @param {OpenSeadragon.EventHandler} [options.keyHandler=null] * An optional handler for keypress. * @param {OpenSeadragon.EventHandler} [options.focusHandler=null] @@ -99,6 +113,8 @@ */ $.MouseTracker = function ( options ) { + MOUSETRACKERS.push( this ); + var args = arguments; if ( !$.isPlainObject( options ) ) { @@ -144,24 +160,28 @@ * @memberof OpenSeadragon.MouseTracker# */ this.dblClickDistThreshold = options.dblClickDistThreshold || $.DEFAULT_SETTINGS.dblClickDistThreshold; - this.userData = options.userData || null; - this.stopDelay = options.stopDelay || 50; + this.userData = options.userData || null; + this.stopDelay = options.stopDelay || 50; - this.enterHandler = options.enterHandler || null; - this.exitHandler = options.exitHandler || null; - this.pressHandler = options.pressHandler || null; - this.releaseHandler = options.releaseHandler || null; - this.moveHandler = options.moveHandler || null; - this.scrollHandler = options.scrollHandler || null; - this.clickHandler = options.clickHandler || null; - this.dblClickHandler = options.dblClickHandler || null; - this.dragHandler = options.dragHandler || null; - this.dragEndHandler = options.dragEndHandler || null; - this.pinchHandler = options.pinchHandler || null; - this.stopHandler = options.stopHandler || null; - this.keyHandler = options.keyHandler || null; - this.focusHandler = options.focusHandler || null; - this.blurHandler = options.blurHandler || null; + this.enterHandler = options.enterHandler || null; + this.exitHandler = options.exitHandler || null; + this.pressHandler = options.pressHandler || null; + this.nonPrimaryPressHandler = options.nonPrimaryPressHandler || null; + this.releaseHandler = options.releaseHandler || null; + this.nonPrimaryReleaseHandler = options.nonPrimaryReleaseHandler || null; + this.moveHandler = options.moveHandler || null; + this.scrollHandler = options.scrollHandler || null; + this.clickHandler = options.clickHandler || null; + this.dblClickHandler = options.dblClickHandler || null; + this.dragHandler = options.dragHandler || null; + this.dragEndHandler = options.dragEndHandler || null; + this.pinchHandler = options.pinchHandler || null; + this.stopHandler = options.stopHandler || null; + this.keyDownHandler = options.keyDownHandler || null; + this.keyUpHandler = options.keyUpHandler || null; + this.keyHandler = options.keyHandler || null; + this.focusHandler = options.focusHandler || null; + this.blurHandler = options.blurHandler || null; //Store private properties in a scope sealed hash map var _this = this; @@ -170,12 +190,12 @@ * @private * @property {Boolean} tracking * Are we currently tracking pointer events for this element. - * @property {Boolean} capturing - * Are we curruently capturing mouse events (legacy mouse events only). */ THIS[ this.hash ] = { click: function ( event ) { onClick( _this, event ); }, dblclick: function ( event ) { onDblClick( _this, event ); }, + keydown: function ( event ) { onKeyDown( _this, event ); }, + keyup: function ( event ) { onKeyUp( _this, event ); }, keypress: function ( event ) { onKeyPress( _this, event ); }, focus: function ( event ) { onFocus( _this, event ); }, blur: function ( event ) { onBlur( _this, event ); }, @@ -185,6 +205,8 @@ DOMMouseScroll: function ( event ) { onMouseWheel( _this, event ); }, MozMousePixelScroll: function ( event ) { onMouseWheel( _this, event ); }, + mouseenter: function ( event ) { onMouseEnter( _this, event ); }, // Used on IE8 only + mouseleave: function ( event ) { onMouseLeave( _this, event ); }, // Used on IE8 only mouseover: function ( event ) { onMouseOver( _this, event ); }, mouseout: function ( event ) { onMouseOut( _this, event ); }, mousedown: function ( event ) { onMouseDown( _this, event ); }, @@ -193,13 +215,11 @@ mousemove: function ( event ) { onMouseMove( _this, event ); }, mousemovecaptured: function ( event ) { onMouseMoveCaptured( _this, event ); }, - mouseoutdocument: function ( event ) { onMouseOutDocument( _this, event ); }, - - touchenter: function ( event ) { onTouchEnter( _this, event ); }, - touchleave: function ( event ) { onTouchLeave( _this, event ); }, touchstart: function ( event ) { onTouchStart( _this, event ); }, touchend: function ( event ) { onTouchEnd( _this, event ); }, + touchendcaptured: function ( event ) { onTouchEndCaptured( _this, event ); }, touchmove: function ( event ) { onTouchMove( _this, event ); }, + touchmovecaptured: function ( event ) { onTouchMoveCaptured( _this, event ); }, touchcancel: function ( event ) { onTouchCancel( _this, event ); }, gesturestart: function ( event ) { onGestureStart( _this, event ); }, @@ -209,10 +229,6 @@ MSPointerOver: function ( event ) { onPointerOver( _this, event ); }, pointerout: function ( event ) { onPointerOut( _this, event ); }, MSPointerOut: function ( event ) { onPointerOut( _this, event ); }, - - pointeroutdocument: function ( event ) { onPointerOutDocument( _this, event ); }, - MSPointerOutdocument: function ( event ) { onPointerOutDocument( _this, event ); }, - pointerdown: function ( event ) { onPointerDown( _this, event ); }, MSPointerDown: function ( event ) { onPointerDown( _this, event ); }, pointerup: function ( event ) { onPointerUp( _this, event ); }, @@ -232,12 +248,6 @@ // of the element (for hover-capable devices) and/or have contact or a button press initiated in the element. activePointersLists: [], - // Legacy mouse capture tracking - capturing: false, - - // Pointer event model capture tracking - pointerCaptureCount: 0, - // Tracking for double-click gesture lastClickPos: null, dblClickTimeOut: null, @@ -250,6 +260,9 @@ currentPinchCenter: null }; + if ( !options.startDisabled ) { + this.setTracking( true ); + } }; $.MouseTracker.prototype = /** @lends OpenSeadragon.MouseTracker.prototype */{ @@ -259,9 +272,18 @@ * @function */ destroy: function () { + var i; + stopTracking( this ); this.element = null; + for ( i = 0; i < MOUSETRACKERS.length; i++ ) { + if ( MOUSETRACKERS[ i ] === this ) { + MOUSETRACKERS.splice( i, 1 ); + break; + } + } + THIS[ this.hash ] = null; delete THIS[ this.hash ]; }, @@ -316,6 +338,24 @@ return list; }, + /** + * Returns the total number of pointers currently active on the tracked element. + * @function + * @returns {Number} + */ + getActivePointerCount: function () { + var delegate = THIS[ this.hash ], + i, + len = delegate.activePointersLists.length, + count = 0; + + for ( i = 0; i < len; i++ ) { + count += delegate.activePointersLists[ i ].getLength(); + } + + return count; + }, + /** * Implement or assign implementation to these handlers during or after * calling the constructor. @@ -330,6 +370,8 @@ * @param {Number} event.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. + * @param {Number} event.pointers + * Number of pointers (all types) active in the tracked element. * @param {Boolean} event.insideElementPressed * True if the left mouse button is currently being pressed and was * initiated inside the tracked element, otherwise false. @@ -360,6 +402,8 @@ * @param {Number} event.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. + * @param {Number} event.pointers + * Number of pointers (all types) active in the tracked element. * @param {Boolean} event.insideElementPressed * True if the left mouse button is currently being pressed and was * initiated inside the tracked element, otherwise false. @@ -401,6 +445,34 @@ */ pressHandler: function () { }, + /** + * 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 {String} event.pointerType + * "mouse", "touch", "pen", etc. + * @param {OpenSeadragon.Point} event.position + * The position of the event relative to the tracked element. + * @param {Number} event.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. + * @param {Number} event.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. + * @param {Boolean} event.isTouchEvent + * True if the original event is a touch event, otherwise false. Deprecated. Use pointerType and/or originalEvent instead. + * @param {Object} event.originalEvent + * The original event object. + * @param {Boolean} event.preventDefaultAction + * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. + * @param {Object} event.userData + * Arbitrary user-defined object. + */ + nonPrimaryPressHandler: function () { }, + /** * Implement or assign implementation to these handlers during or after * calling the constructor. @@ -431,6 +503,34 @@ */ releaseHandler: function () { }, + /** + * 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 {String} event.pointerType + * "mouse", "touch", "pen", etc. + * @param {OpenSeadragon.Point} event.position + * The position of the event relative to the tracked element. + * @param {Number} event.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. + * @param {Number} event.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. + * @param {Boolean} event.isTouchEvent + * True if the original event is a touch event, otherwise false. Deprecated. Use pointerType and/or originalEvent instead. + * @param {Object} event.originalEvent + * The original event object. + * @param {Boolean} event.preventDefaultAction + * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. + * @param {Object} event.userData + * Arbitrary user-defined object. + */ + nonPrimaryReleaseHandler: function () { }, + /** * Implement or assign implementation to these handlers during or after * calling the constructor. @@ -657,8 +757,66 @@ * A reference to the tracker instance. * @param {Number} event.keyCode * The key code that was pressed. + * @param {Boolean} event.ctrl + * True if the ctrl key was pressed during this event. * @param {Boolean} event.shift * True if the shift key was pressed during this event. + * @param {Boolean} event.alt + * True if the alt key was pressed during this event. + * @param {Boolean} event.meta + * True if the meta key was pressed during this event. + * @param {Object} event.originalEvent + * The original event object. + * @param {Boolean} event.preventDefaultAction + * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. + * @param {Object} event.userData + * Arbitrary user-defined object. + */ + keyDownHandler: function () { }, + + /** + * 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 {Number} event.keyCode + * The key code that was pressed. + * @param {Boolean} event.ctrl + * True if the ctrl key was pressed during this event. + * @param {Boolean} event.shift + * True if the shift key was pressed during this event. + * @param {Boolean} event.alt + * True if the alt key was pressed during this event. + * @param {Boolean} event.meta + * True if the meta key was pressed during this event. + * @param {Object} event.originalEvent + * The original event object. + * @param {Boolean} event.preventDefaultAction + * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. + * @param {Object} event.userData + * Arbitrary user-defined object. + */ + keyUpHandler: function () { }, + + /** + * 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 {Number} event.keyCode + * The key code that was pressed. + * @param {Boolean} event.ctrl + * True if the ctrl key was pressed during this event. + * @param {Boolean} event.shift + * True if the shift key was pressed during this event. + * @param {Boolean} event.alt + * True if the alt key was pressed during this event. + * @param {Boolean} event.meta + * True if the meta key was pressed during this event. * @param {Object} event.originalEvent * The original event object. * @param {Boolean} event.preventDefaultAction @@ -797,6 +955,8 @@ // Pointer event model and feature detection /////////////////////////////////////////////////////////////////////////////// + $.MouseTracker.captureElement = document; + /** * Detect available mouse wheel event name. */ @@ -816,15 +976,17 @@ /** * Detect browser pointer device event model(s) and build appropriate list of events to subscribe to. */ - $.MouseTracker.subscribeEvents = [ "click", "dblclick", "keypress", "focus", "blur", $.MouseTracker.wheelEventName ]; + $.MouseTracker.subscribeEvents = [ "click", "dblclick", "keydown", "keyup", "keypress", "focus", "blur", $.MouseTracker.wheelEventName ]; if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) { // Older Firefox $.MouseTracker.subscribeEvents.push( "MozMousePixelScroll" ); } - if ( window.PointerEvent ) { + // Note: window.navigator.pointerEnable is deprecated on IE 11 and not part of W3C spec. + if ( window.PointerEvent && ( window.navigator.pointerEnabled || $.Browser.vendor !== $.BROWSERS.IE ) ) { // IE11 and other W3C Pointer Event implementations (see http://www.w3.org/TR/pointerevents) + $.MouseTracker.havePointerEvents = true; $.MouseTracker.subscribeEvents.push( "pointerover", "pointerout", "pointerdown", "pointerup", "pointermove", "pointercancel" ); $.MouseTracker.unprefixedPointerEvents = true; if( navigator.maxTouchPoints ) { @@ -832,10 +994,10 @@ } else { $.MouseTracker.maxTouchPoints = 0; } - $.MouseTracker.haveTouchEnter = false; $.MouseTracker.haveMouseEnter = false; - } else if ( window.MSPointerEvent ) { + } else if ( window.MSPointerEvent && window.navigator.msPointerEnabled ) { // IE10 + $.MouseTracker.havePointerEvents = true; $.MouseTracker.subscribeEvents.push( "MSPointerOver", "MSPointerOut", "MSPointerDown", "MSPointerUp", "MSPointerMove", "MSPointerCancel" ); $.MouseTracker.unprefixedPointerEvents = false; if( navigator.msMaxTouchPoints ) { @@ -843,26 +1005,27 @@ } else { $.MouseTracker.maxTouchPoints = 0; } - $.MouseTracker.haveTouchEnter = false; $.MouseTracker.haveMouseEnter = false; } else { // Legacy W3C mouse events - $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout", "mousedown", "mouseup", "mousemove" ); - $.MouseTracker.haveMouseEnter = false; - if ( 'ontouchstart' in window ) { - // iOS, Android, and other W3c Touch Event implementations (see http://www.w3.org/TR/2011/WD-touch-events-20110505) - $.MouseTracker.subscribeEvents.push( "touchstart", "touchend", "touchmove", "touchcancel" ); - if ( 'ontouchenter' in window ) { - $.MouseTracker.subscribeEvents.push( "touchenter", "touchleave" ); - $.MouseTracker.haveTouchEnter = true; - } else { - $.MouseTracker.haveTouchEnter = false; - } + $.MouseTracker.havePointerEvents = false; + if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { + $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" ); + $.MouseTracker.haveMouseEnter = true; } else { - $.MouseTracker.haveTouchEnter = false; + $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout" ); + $.MouseTracker.haveMouseEnter = false; + } + $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" ); + if ( 'ontouchstart' in window ) { + // iOS, Android, and other W3c Touch Event implementations + // (see http://www.w3.org/TR/touch-events/) + // (see https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html) + // (see https://developer.apple.com/library/safari/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html) + $.MouseTracker.subscribeEvents.push( "touchstart", "touchend", "touchmove", "touchcancel" ); } if ( 'ongesturestart' in window ) { - // iOS (see https://developer.apple.com/library/safari/documentation/UserExperience/Reference/GestureEventClassReference/GestureEvent/GestureEvent.html) + // iOS (see https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html) // Subscribe to these to prevent default gesture handling $.MouseTracker.subscribeEvents.push( "gesturestart", "gesturechange" ); } @@ -947,6 +1110,12 @@ * @memberof OpenSeadragon.MouseTracker.GesturePointList# */ this.clicks = 0; + /** + * Current number of captured pointers for the device. + * @member {Number} captureCount + * @memberof OpenSeadragon.MouseTracker.GesturePointList# + */ + this.captureCount = 0; }; $.MouseTracker.GesturePointList.prototype = /** @lends OpenSeadragon.MouseTracker.GesturePointList.prototype */{ /** @@ -1035,6 +1204,64 @@ // Utility functions /////////////////////////////////////////////////////////////////////////////// + /** + * Removes all tracked pointers. + * @private + * @inner + */ + function clearTrackedPointers( tracker ) { + var delegate = THIS[ tracker.hash ], + i, + pointerListCount = delegate.activePointersLists.length; + + for ( i = 0; i < pointerListCount; i++ ) { + if ( delegate.activePointersLists[ i ].captureCount > 0 ) { + $.removeEvent( + $.MouseTracker.captureElement, + 'mousemove', + delegate.mousemovecaptured, + true + ); + $.removeEvent( + $.MouseTracker.captureElement, + 'mouseup', + delegate.mouseupcaptured, + true + ); + $.removeEvent( + $.MouseTracker.captureElement, + $.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove', + delegate.pointermovecaptured, + true + ); + $.removeEvent( + $.MouseTracker.captureElement, + $.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp', + delegate.pointerupcaptured, + true + ); + $.removeEvent( + $.MouseTracker.captureElement, + 'touchmove', + delegate.touchmovecaptured, + true + ); + $.removeEvent( + $.MouseTracker.captureElement, + 'touchend', + delegate.touchendcaptured, + true + ); + + delegate.activePointersLists[ i ].captureCount = 0; + } + } + + for ( i = 0; i < pointerListCount; i++ ) { + delegate.activePointersLists.pop(); + } + } + /** * Starts tracking pointer events on the tracked element. * @private @@ -1056,14 +1283,7 @@ ); } - // handle pointer/mouse out of document body - if ( window.PointerEvent ) { - $.addEvent(document.body, "pointerout", delegate.pointeroutdocument); - } else if ( window.MSPointerEvent ) { - $.addEvent(document.body, "pointerout", delegate.MSPointerOutdocument); - } else { - $.addEvent(document.body, "mouseout", delegate.mouseoutdocument); - } + clearTrackedPointers( tracker ); delegate.tracking = true; } @@ -1090,45 +1310,76 @@ ); } - // handle pointer/mouse out of document body - if ( window.PointerEvent ) { - $.removeEvent(document.body, "pointerout", delegate.pointeroutdocument); - } else if ( window.MSPointerEvent ) { - $.removeEvent(document.body, "MSPointerOut", delegate.MSPointerOutdocument); - } else { - $.removeEvent(document.body, "mouseout", delegate.mouseoutdocument); - } + clearTrackedPointers( tracker ); delegate.tracking = false; } } + /** + * @private + * @inner + */ + function getCaptureEventParams( tracker, pointerType ) { + var delegate = THIS[ tracker.hash ]; + + if ( pointerType === 'pointerevent' ) { + return { + upName: $.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp', + upHandler: delegate.pointerupcaptured, + moveName: $.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove', + moveHandler: delegate.pointermovecaptured + }; + } else if ( pointerType === 'mouse' ) { + return { + upName: 'mouseup', + upHandler: delegate.mouseupcaptured, + moveName: 'mousemove', + moveHandler: delegate.mousemovecaptured + }; + } else if ( pointerType === 'touch' ) { + return { + upName: 'touchend', + upHandler: delegate.touchendcaptured, + moveName: 'touchmove', + moveHandler: delegate.touchmovecaptured + }; + } else { + throw new Error( "MouseTracker.getCaptureEventParams: Unknown pointer type." ); + } + } + /** * Begin capturing pointer events to the tracked element. * @private * @inner */ - function capturePointer( tracker, isLegacyMouse ) { - var delegate = THIS[ tracker.hash ]; + function capturePointer( tracker, pointerType ) { + var pointsList = tracker.getActivePointersListByType( pointerType ), + eventParams; - delegate.pointerCaptureCount++; - //$.console.log('pointerCaptureCount++ ', delegate.pointerCaptureCount); + pointsList.captureCount++; - if ( delegate.pointerCaptureCount === 1 ) { - // We emulate mouse capture by hanging listeners on the window object. - // (Note we listen on the capture phase so the captured handlers will get called first) - $.addEvent( - window, - isLegacyMouse ? 'mouseup' : ($.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp'), - isLegacyMouse ? delegate.mouseupcaptured : delegate.pointerupcaptured, - true - ); - $.addEvent( - window, - isLegacyMouse ? 'mousemove' : ($.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove'), - isLegacyMouse ? delegate.mousemovecaptured : delegate.pointermovecaptured, - true - ); + if ( pointsList.captureCount === 1 ) { + if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { + tracker.element.setCapture( true ); + } else { + eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : pointerType ); + // We emulate mouse capture by hanging listeners on the document object. + // (Note we listen on the capture phase so the captured handlers will get called first) + $.addEvent( + $.MouseTracker.captureElement, + eventParams.upName, + eventParams.upHandler, + true + ); + $.addEvent( + $.MouseTracker.captureElement, + eventParams.moveName, + eventParams.moveHandler, + true + ); + } } } @@ -1138,27 +1389,32 @@ * @private * @inner */ - function releasePointer( tracker, isLegacyMouse ) { - var delegate = THIS[ tracker.hash ]; + function releasePointer( tracker, pointerType ) { + var pointsList = tracker.getActivePointersListByType( pointerType ), + eventParams; - delegate.pointerCaptureCount--; - //$.console.log('pointerCaptureCount-- ', delegate.pointerCaptureCount); + pointsList.captureCount--; - if ( delegate.pointerCaptureCount === 0 ) { - // We emulate mouse capture by hanging listeners on the window object. - // (Note we listen on the capture phase so the captured handlers will get called first) - $.removeEvent( - window, - isLegacyMouse ? 'mousemove' : ($.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove'), - isLegacyMouse ? delegate.mousemovecaptured : delegate.pointermovecaptured, - true - ); - $.removeEvent( - window, - isLegacyMouse ? 'mouseup' : ($.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp'), - isLegacyMouse ? delegate.mouseupcaptured : delegate.pointerupcaptured, - true - ); + if ( pointsList.captureCount === 0 ) { + if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { + tracker.element.releaseCapture(); + } else { + eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : pointerType ); + // We emulate mouse capture by hanging listeners on the document object. + // (Note we listen on the capture phase so the captured handlers will get called first) + $.removeEvent( + $.MouseTracker.captureElement, + eventParams.moveName, + eventParams.moveHandler, + true + ); + $.removeEvent( + $.MouseTracker.captureElement, + eventParams.upName, + eventParams.upHandler, + true + ); + } } } @@ -1257,21 +1513,81 @@ } + /** + * @private + * @inner + */ + function onKeyDown( tracker, event ) { + //$.console.log( "keydown %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey ); + var propagate; + if ( tracker.keyDownHandler ) { + event = $.getEvent( event ); + propagate = tracker.keyDownHandler( + { + eventSource: tracker, + keyCode: event.keyCode ? event.keyCode : event.charCode, + ctrl: event.ctrlKey, + shift: event.shiftKey, + alt: event.altKey, + meta: event.metaKey, + originalEvent: event, + preventDefaultAction: false, + userData: tracker.userData + } + ); + if ( !propagate ) { + $.cancelEvent( event ); + } + } + } + + + /** + * @private + * @inner + */ + function onKeyUp( tracker, event ) { + //$.console.log( "keyup %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey ); + var propagate; + if ( tracker.keyUpHandler ) { + event = $.getEvent( event ); + propagate = tracker.keyUpHandler( + { + eventSource: tracker, + keyCode: event.keyCode ? event.keyCode : event.charCode, + ctrl: event.ctrlKey, + shift: event.shiftKey, + alt: event.altKey, + meta: event.metaKey, + originalEvent: event, + preventDefaultAction: false, + userData: tracker.userData + } + ); + if ( !propagate ) { + $.cancelEvent( event ); + } + } + } + + /** * @private * @inner */ function onKeyPress( tracker, event ) { - //console.log( "keypress %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey ); + //$.console.log( "keypress %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey ); var propagate; if ( tracker.keyHandler ) { event = $.getEvent( event ); propagate = tracker.keyHandler( { eventSource: tracker, - position: getMouseRelative( event, tracker.element ), keyCode: event.keyCode ? event.keyCode : event.charCode, + ctrl: event.ctrlKey, shift: event.shiftKey, + alt: event.altKey, + meta: event.metaKey, originalEvent: event, preventDefaultAction: false, userData: tracker.userData @@ -1432,20 +1748,40 @@ } + /** + * Only used on IE 8 + * + * @private + * @inner + */ + function onMouseEnter( tracker, event ) { + event = $.getEvent( event ); + + handleMouseEnter( tracker, event ); + } + + /** * @private * @inner */ function onMouseOver( tracker, event ) { - var gPoint; - event = $.getEvent( event ); - if ( this === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { + if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { return; } - gPoint = { + handleMouseEnter( tracker, event ); + } + + + /** + * @private + * @inner + */ + function handleMouseEnter( tracker, event ) { + var gPoint = { id: $.MouseTracker.mousePointerId, type: 'mouse', isPrimary: true, @@ -1457,20 +1793,40 @@ } + /** + * Only used on IE 8 + * + * @private + * @inner + */ + function onMouseLeave( tracker, event ) { + event = $.getEvent( event ); + + handleMouseExit( tracker, event ); + } + + /** * @private * @inner */ function onMouseOut( tracker, event ) { - var gPoint; - event = $.getEvent( event ); - if ( this === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { + if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { return; } - gPoint = { + handleMouseExit( tracker, event ); + } + + + /** + * @private + * @inner + */ + function handleMouseExit( tracker, event ) { + var gPoint = { id: $.MouseTracker.mousePointerId, type: 'mouse', isPrimary: true, @@ -1481,36 +1837,32 @@ updatePointersExit( tracker, event, [ gPoint ] ); } + /** - * This handler is used to handle the case where the mouse is dragged out of the window, it should cause the drag to be properly released. - * + * Returns a W3C DOM level 3 standard button value given an event.button property: + * -1 == none, 0 == primary/left, 1 == middle, 2 == secondary/right, 3 == X1/back, 4 == X2/forward, 5 == eraser (pen) * @private * @inner */ - function onMouseOutDocument( tracker, event ) { - event = $.getEvent( event ); - - var html = document.getElementsByTagName("html")[0]; - var target = event.target || event.srcElement; - if ((event.relatedTarget!==html && event.relatedTarget!==null) || event.currentTarget !== document.body) { - return; // not a mouseout of the iframe - } - - var gPoint = { - id: $.MouseTracker.mousePointerId, - type: 'mouse', - isPrimary: true, - currentPos: getMouseAbsolute( event ), - currentTime: $.now() - }; - - event.buttons = undefined; - - if ( updatePointersUp( tracker, event, [ gPoint ], 0 ) ) { - releasePointer( tracker, true ); + function getStandardizedButton( button ) { + if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { + // On IE 8, 0 == none, 1 == left, 2 == right, 3 == left and right, 4 == middle, 5 == left and middle, 6 == right and middle, 7 == all three + // TODO: Support chorded (multiple) button presses on IE 8? + if ( button === 1 ) { + return 0; + } else if ( button === 2 ) { + return 2; + } else if ( button === 4 ) { + return 1; + } else { + return -1; + } + } else { + return button; } } + /** * @private * @inner @@ -1528,9 +1880,9 @@ currentTime: $.now() }; - if ( updatePointersDown( tracker, event, [ gPoint ], event.button ) ) { + if ( updatePointersDown( tracker, event, [ gPoint ], getStandardizedButton( event.button ) ) ) { $.stopEvent( event ); - capturePointer( tracker, true ); + capturePointer( tracker, 'mouse' ); } if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler ) { @@ -1577,11 +1929,12 @@ currentTime: $.now() }; - if ( updatePointersUp( tracker, event, [ gPoint ], event.button ) ) { - releasePointer( tracker, true ); + if ( updatePointersUp( tracker, event, [ gPoint ], getStandardizedButton( event.button ) ) ) { + releasePointer( tracker, 'mouse' ); } } + /** * @private * @inner @@ -1629,45 +1982,24 @@ * @private * @inner */ - function onTouchEnter( tracker, event ) { + function abortTouchContacts( tracker, event, pointsList ) { var i, - touchCount = event.changedTouches.length, - gPoints = []; + gPointCount = pointsList.getLength(), + abortGPoints = []; - for ( i = 0; i < touchCount; i++ ) { - gPoints.push( { - id: event.changedTouches[ i ].identifier, - type: 'touch', - // isPrimary not set - let the updatePointers functions determine it - currentPos: getMouseAbsolute( event.changedTouches[ i ] ), - currentTime: $.now() - } ); + for ( i = 0; i < gPointCount; i++ ) { + abortGPoints.push( pointsList.getByIndex( i ) ); } - updatePointersEnter( tracker, event, gPoints ); - } - - - /** - * @private - * @inner - */ - function onTouchLeave( tracker, event ) { - var i, - touchCount = event.changedTouches.length, - gPoints = []; - - for ( i = 0; i < touchCount; i++ ) { - gPoints.push( { - id: event.changedTouches[ i ].identifier, - type: 'touch', - // isPrimary not set - let the updatePointers functions determine it - currentPos: getMouseAbsolute( event.changedTouches[ i ] ), - currentTime: $.now() - } ); + if ( abortGPoints.length > 0 ) { + // simulate touchend + updatePointersUp( tracker, event, abortGPoints, 0 ); // 0 means primary button press/release or touch contact + // release pointer capture + pointsList.captureCount = 1; + releasePointer( tracker, 'touch' ); + // simulate touchleave + updatePointersExit( tracker, event, abortGPoints ); } - - updatePointersExit( tracker, event, gPoints ); } @@ -1678,11 +2010,19 @@ function onTouchStart( tracker, event ) { var time, i, + j, touchCount = event.changedTouches.length, - gPoints = []; + gPoints = [], + parentGPoints, + pointsList = tracker.getActivePointersListByType( 'touch' ); time = $.now(); + if ( pointsList.getLength() > event.touches.length - touchCount ) { + $.console.warn('Tracked touch contact count doesn\'t match event.touches.length. Removing all tracked touch pointers.'); + abortTouchContacts( tracker, event, pointsList ); + } + for ( i = 0; i < touchCount; i++ ) { gPoints.push( { id: event.changedTouches[ i ].identifier, @@ -1693,13 +2033,29 @@ } ); } - // simulate touchenter if not natively available - if ( !$.MouseTracker.haveTouchEnter ) { - updatePointersEnter( tracker, event, gPoints ); + // simulate touchenter on our tracked element + updatePointersEnter( tracker, event, gPoints ); + + // simulate touchenter on our tracked element's tracked ancestor elements + for ( i = 0; i < MOUSETRACKERS.length; i++ ) { + if ( MOUSETRACKERS[ i ] !== tracker && MOUSETRACKERS[ i ].isTracking() && isParentChild( MOUSETRACKERS[ i ].element, tracker.element ) ) { + parentGPoints = []; + for ( j = 0; j < touchCount; j++ ) { + parentGPoints.push( { + id: event.changedTouches[ j ].identifier, + type: 'touch', + // isPrimary not set - let the updatePointers functions determine it + currentPos: getMouseAbsolute( event.changedTouches[ j ] ), + currentTime: time + } ); + } + updatePointersEnter( MOUSETRACKERS[ i ], event, parentGPoints ); + } } if ( updatePointersDown( tracker, event, gPoints, 0 ) ) { // 0 means primary button press/release or touch contact - // Touch event model start, end, and move events are always captured so we don't need to capture explicitly + $.stopEvent( event ); + capturePointer( tracker, 'touch' ); } $.cancelEvent( event ); @@ -1711,10 +2067,34 @@ * @inner */ function onTouchEnd( tracker, event ) { + handleTouchEnd( tracker, event ); + } + + + /** + * This handler is attached to the window object (on the capture phase) to emulate pointer capture. + * onTouchEnd is still attached to the tracked element, so stop propagation to avoid processing twice. + * + * @private + * @inner + */ + function onTouchEndCaptured( tracker, event ) { + handleTouchEnd( tracker, event ); + $.stopEvent( event ); + } + + + /** + * @private + * @inner + */ + function handleTouchEnd( tracker, event ) { var time, i, + j, touchCount = event.changedTouches.length, - gPoints = []; + gPoints = [], + parentGPoints; time = $.now(); @@ -1728,13 +2108,28 @@ } ); } - // Touch event model start, end, and move events are always captured so we don't need to release capture. - // We'll ignore the should-release-capture return value here - updatePointersUp( tracker, event, gPoints, 0 ); // 0 means primary button press/release or touch contact + if ( updatePointersUp( tracker, event, gPoints, 0 ) ) { + releasePointer( tracker, 'touch' ); + } - // simulate touchleave if not natively available - if ( !$.MouseTracker.haveTouchEnter && touchCount > 0 ) { - updatePointersExit( tracker, event, gPoints ); + // simulate touchleave on our tracked element + updatePointersExit( tracker, event, gPoints ); + + // simulate touchleave on our tracked element's tracked ancestor elements + for ( i = 0; i < MOUSETRACKERS.length; i++ ) { + if ( MOUSETRACKERS[ i ] !== tracker && MOUSETRACKERS[ i ].isTracking() && isParentChild( MOUSETRACKERS[ i ].element, tracker.element ) ) { + parentGPoints = []; + for ( j = 0; j < touchCount; j++ ) { + parentGPoints.push( { + id: event.changedTouches[ j ].identifier, + type: 'touch', + // isPrimary not set - let the updatePointers functions determine it + currentPos: getMouseAbsolute( event.changedTouches[ j ] ), + currentTime: time + } ); + } + updatePointersExit( MOUSETRACKERS[ i ], event, parentGPoints ); + } } $.cancelEvent( event ); @@ -1746,6 +2141,28 @@ * @inner */ function onTouchMove( tracker, event ) { + handleTouchMove( tracker, event ); + } + + + /** + * This handler is attached to the window object (on the capture phase) to emulate pointer capture. + * onTouchMove is still attached to the tracked element, so stop propagation to avoid processing twice. + * + * @private + * @inner + */ + function onTouchMoveCaptured( tracker, event ) { + handleTouchMove( tracker, event ); + $.stopEvent( event ); + } + + + /** + * @private + * @inner + */ + function handleTouchMove( tracker, event ) { var i, touchCount = event.changedTouches.length, gPoints = []; @@ -1815,7 +2232,7 @@ function onPointerOver( tracker, event ) { var gPoint; - if ( this === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { + if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { return; } @@ -1838,7 +2255,7 @@ function onPointerOut( tracker, event ) { var gPoint; - if ( this === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { + if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { return; } @@ -1853,32 +2270,6 @@ updatePointersExit( tracker, event, [ gPoint ] ); } - /** - * This handler is used to handle the case where the pointer is dragged out of the window, it should cause the drag to be properly released. - * - * @private - * @inner - */ - function onPointerOutDocument( tracker, event ) { - event = $.getEvent( event ); - - var html = document.getElementsByTagName("html")[0]; - if ((event.relatedTarget!==html && event.relatedTarget!==null) || event.currentTarget !== document.body) { - return; // not a mouseout of the iframe - } - - var gPoint = { - id: event.pointerId, - type: getPointerType( event ), - isPrimary: event.isPrimary, - currentPos: getMouseAbsolute( event ), - currentTime: $.now() - }; - - if ( updatePointersUp( tracker, event, [ gPoint ], 0 ) ) { - releasePointer( tracker, false ); - } - } /** * @private @@ -1896,8 +2287,8 @@ }; if ( updatePointersDown( tracker, event, [ gPoint ], event.button ) ) { - capturePointer( tracker, false ); $.stopEvent( event ); + capturePointer( tracker, gPoint.type ); } if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) { @@ -1947,8 +2338,7 @@ }; if ( updatePointersUp( tracker, event, [ gPoint ], event.button ) ) { - releasePointer( tracker, false ); - //$.stopEvent( event ); + releasePointer( tracker, gPoint.type ); } } @@ -2133,6 +2523,7 @@ pointerType: curGPoint.type, position: getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ), buttons: pointsList.buttons, + pointers: tracker.getActivePointerCount(), insideElementPressed: curGPoint.insideElementPressed, buttonDownAny: pointsList.buttons !== 0, isTouchEvent: curGPoint.type === 'touch', @@ -2196,6 +2587,7 @@ pointerType: curGPoint.type, position: getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ), buttons: pointsList.buttons, + pointers: tracker.getActivePointerCount(), insideElementPressed: updateGPoint ? updateGPoint.insideElementPressed : false, buttonDownAny: pointsList.buttons !== 0, isTouchEvent: curGPoint.type === 'touch', @@ -2224,7 +2616,7 @@ * @param {Array.} gPoints * Gesture points associated with the event. * @param {Number} buttonChanged - * The button involved in the event: -1: none, 0: primary, 1: aux, 2: secondary, 3: X1, 4: X2, 5: pen eraser. + * The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser. * Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model, * only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events. * @@ -2242,30 +2634,71 @@ if ( typeof event.buttons !== 'undefined' ) { pointsList.buttons = event.buttons; } else { - if ( buttonChanged === 0 ) { - // Primary - pointsList.buttons |= 1; - } else if ( buttonChanged === 1 ) { - // Aux - pointsList.buttons |= 4; - } else if ( buttonChanged === 2 ) { - // Secondary - pointsList.buttons |= 2; - } else if ( buttonChanged === 3 ) { - // X1 (Back) - pointsList.buttons |= 8; - } else if ( buttonChanged === 4 ) { - // X2 (Forward) - pointsList.buttons |= 16; - } else if ( buttonChanged === 5 ) { - // Pen Eraser - pointsList.buttons |= 32; + if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { + if ( buttonChanged === 0 ) { + // Primary + pointsList.buttons += 1; + } else if ( buttonChanged === 1 ) { + // Aux + pointsList.buttons += 4; + } else if ( buttonChanged === 2 ) { + // Secondary + pointsList.buttons += 2; + } else if ( buttonChanged === 3 ) { + // X1 (Back) + pointsList.buttons += 8; + } else if ( buttonChanged === 4 ) { + // X2 (Forward) + pointsList.buttons += 16; + } else if ( buttonChanged === 5 ) { + // Pen Eraser + pointsList.buttons += 32; + } + } else { + if ( buttonChanged === 0 ) { + // Primary + pointsList.buttons |= 1; + } else if ( buttonChanged === 1 ) { + // Aux + pointsList.buttons |= 4; + } else if ( buttonChanged === 2 ) { + // Secondary + pointsList.buttons |= 2; + } else if ( buttonChanged === 3 ) { + // X1 (Back) + pointsList.buttons |= 8; + } else if ( buttonChanged === 4 ) { + // X2 (Forward) + pointsList.buttons |= 16; + } else if ( buttonChanged === 5 ) { + // Pen Eraser + pointsList.buttons |= 32; + } } } // Only capture and track primary button, pen, and touch contacts - //if ( buttonChanged !== 0 ) { - if ( buttonChanged !== 0 && buttonChanged !== 1 ) { //TODO Remove this IE8 compatibility and use the commented line above + if ( buttonChanged !== 0 ) { + // Aux Press + if ( tracker.nonPrimaryPressHandler ) { + propagate = tracker.nonPrimaryPressHandler( + { + eventSource: tracker, + pointerType: gPoints[ 0 ].type, + position: getPointRelativeToAbsolute( gPoints[ 0 ].currentPos, tracker.element ), + button: buttonChanged, + buttons: pointsList.buttons, + isTouchEvent: gPoints[ 0 ].type === 'touch', + originalEvent: event, + preventDefaultAction: false, + userData: tracker.userData + } + ); + if ( propagate === false ) { + $.cancelEvent( event ); + } + } + return false; } @@ -2345,7 +2778,7 @@ * @param {Array.} gPoints * Gesture points associated with the event. * @param {Number} buttonChanged - * The button involved in the event: -1: none, 0: primary, 1: aux, 2: secondary, 3: X1, 4: X2, 5: pen eraser. + * The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser. * Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model, * only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events. * @@ -2369,30 +2802,71 @@ if ( typeof event.buttons !== 'undefined' ) { pointsList.buttons = event.buttons; } else { - if ( buttonChanged === 0 ) { - // Primary - pointsList.buttons ^= ~1; - } else if ( buttonChanged === 1 ) { - // Aux - pointsList.buttons ^= ~4; - } else if ( buttonChanged === 2 ) { - // Secondary - pointsList.buttons ^= ~2; - } else if ( buttonChanged === 3 ) { - // X1 (Back) - pointsList.buttons ^= ~8; - } else if ( buttonChanged === 4 ) { - // X2 (Forward) - pointsList.buttons ^= ~16; - } else if ( buttonChanged === 5 ) { - // Pen Eraser - pointsList.buttons ^= ~32; + if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { + if ( buttonChanged === 0 ) { + // Primary + pointsList.buttons -= 1; + } else if ( buttonChanged === 1 ) { + // Aux + pointsList.buttons -= 4; + } else if ( buttonChanged === 2 ) { + // Secondary + pointsList.buttons -= 2; + } else if ( buttonChanged === 3 ) { + // X1 (Back) + pointsList.buttons -= 8; + } else if ( buttonChanged === 4 ) { + // X2 (Forward) + pointsList.buttons -= 16; + } else if ( buttonChanged === 5 ) { + // Pen Eraser + pointsList.buttons -= 32; + } + } else { + if ( buttonChanged === 0 ) { + // Primary + pointsList.buttons ^= ~1; + } else if ( buttonChanged === 1 ) { + // Aux + pointsList.buttons ^= ~4; + } else if ( buttonChanged === 2 ) { + // Secondary + pointsList.buttons ^= ~2; + } else if ( buttonChanged === 3 ) { + // X1 (Back) + pointsList.buttons ^= ~8; + } else if ( buttonChanged === 4 ) { + // X2 (Forward) + pointsList.buttons ^= ~16; + } else if ( buttonChanged === 5 ) { + // Pen Eraser + pointsList.buttons ^= ~32; + } } } // Only capture and track primary button, pen, and touch contacts - //if ( buttonChanged !== 0 ) { - if ( buttonChanged !== 0 && buttonChanged !== 1 ) { //TODO Remove this IE8 compatibility and use the commented line above + if ( buttonChanged !== 0 ) { + // Aux Release + if ( tracker.nonPrimaryReleaseHandler ) { + propagate = tracker.nonPrimaryReleaseHandler( + { + eventSource: tracker, + pointerType: gPoints[ 0 ].type, + position: getPointRelativeToAbsolute( gPoints[ 0 ].currentPos, tracker.element ), + button: buttonChanged, + buttons: pointsList.buttons, + isTouchEvent: gPoints[ 0 ].type === 'touch', + originalEvent: event, + preventDefaultAction: false, + userData: tracker.userData + } + ); + if ( propagate === false ) { + $.cancelEvent( event ); + } + } + return false; } diff --git a/src/navigator.js b/src/navigator.js index 9e18d6ca..464cd988 100644 --- a/src/navigator.js +++ b/src/navigator.js @@ -52,8 +52,7 @@ $.Navigator = function( options ){ var viewer = options.viewer, _this = this, viewerSize, - navigatorSize, - unneededElement; + navigatorSize; //We may need to create a new element and id if they did not //provide the id for the existing element @@ -99,6 +98,7 @@ $.Navigator = function( options ){ sizeRatio: $.DEFAULT_SETTINGS.navigatorSizeRatio }, options, { element: this.element, + tabIndex: -1, // No keyboard navigation, omit from tab order //These need to be overridden to prevent recursion since //the navigator is a viewer and a viewer has a navigator showNavigator: false, @@ -168,24 +168,6 @@ $.Navigator = function( options ){ this.displayRegionContainer.style.width = "100%"; this.displayRegionContainer.style.height = "100%"; - this.element.innerTracker = new $.MouseTracker({ - element: this.element, - dragHandler: $.delegate( this, onCanvasDrag ), - clickHandler: $.delegate( this, onCanvasClick ), - releaseHandler: $.delegate( this, onCanvasRelease ), - scrollHandler: $.delegate( this, onCanvasScroll ) - }).setTracking( true ); - - /*this.displayRegion.outerTracker = new $.MouseTracker({ - element: this.container, - clickTimeThreshold: this.clickTimeThreshold, - clickDistThreshold: this.clickDistThreshold, - enterHandler: $.delegate( this, onContainerEnter ), - exitHandler: $.delegate( this, onContainerExit ), - releaseHandler: $.delegate( this, onContainerRelease ) - }).setTracking( this.mouseNavEnabled ? true : false ); // always tracking*/ - - viewer.addControl( this.element, options.controlOptions @@ -211,10 +193,6 @@ $.Navigator = function( options ){ this.displayRegionContainer.appendChild(this.displayRegion); this.element.getElementsByTagName('div')[0].appendChild(this.displayRegionContainer); - unneededElement = this.element.getElementsByTagName('textarea')[0]; - if (unneededElement) { - unneededElement.parentNode.removeChild(unneededElement); - } if (options.navigatorRotate) { @@ -223,8 +201,18 @@ $.Navigator = function( options ){ _setTransformRotate(_this.displayRegion, -args.degrees); _this.viewport.setRotation(args.degrees); }); - } + + // Remove the base class' (Viewer's) innerTracker and replace it with our own + this.innerTracker.destroy(); + this.innerTracker = new $.MouseTracker({ + element: this.element, + dragHandler: $.delegate( this, onCanvasDrag ), + clickHandler: $.delegate( this, onCanvasClick ), + releaseHandler: $.delegate( this, onCanvasRelease ), + scrollHandler: $.delegate( this, onCanvasScroll ) + }); + }; $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /** @lends OpenSeadragon.Navigator.prototype */{ diff --git a/src/openseadragon.js b/src/openseadragon.js index f8b744de..f56f10da 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -126,6 +126,10 @@ * The element to append the viewer's container element to. If not provided, the 'id' property must be provided. * If both the element and id properties are specified, the viewer is appended to the element provided in the element property. * + * @property {Number} [tabIndex=0] + * Tabbing order index to assign to the viewer element. Positive values are selected in increasing order. When tabIndex is 0 + * source order is used. A negative value omits the viewer from the tabbing order. + * * @property {Array|String|Function|Object[]|Array[]|String[]|Function[]} [tileSources=null] * As an Array, the tileSource can hold either Objects or mixed * types of Arrays of Objects, Strings, or Functions. When a value is a String, @@ -515,10 +519,18 @@ * * @property {Boolean} [preserveViewport=false] * If the viewer has been configured with a sequence of tile sources, then - * normally navigating to through each image resets the viewport to 'home' + * normally navigating through each image resets the viewport to 'home' * position. If preserveViewport is set to true, then the viewport position * is preserved when navigating between images in the sequence. * + * @property {Boolean} [preserveOverlays=false] + * If the viewer has been configured with a sequence of tile sources, then + * normally navigating through each image resets the overlays. + * If preserveOverlays is set to true, then the overlays + * are preserved when navigating between images in the sequence. + * Note: setting preserveOverlays overrides any overlays specified in the + * "overlays" property. + * * @property {Boolean} [showReferenceStrip=false] * If the viewer has been configured with a sequence of tile sources, then * display a scrolling strip of image thumbnails for navigating through the images. @@ -945,6 +957,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ showSequenceControl: true, //SEQUENCE sequenceControlAnchor: null, //SEQUENCE preserveViewport: false, //SEQUENCE + preserveOverlays: false, //SEQUENCE navPrevNextWrap: false, //SEQUENCE showNavigationControl: true, //ZOOM/HOME/FULL/ROTATION navigationControlAnchor: null, //ZOOM/HOME/FULL/ROTATION diff --git a/src/referencestrip.js b/src/referencestrip.js index f68744b2..480009ec 100644 --- a/src/referencestrip.js +++ b/src/referencestrip.js @@ -125,8 +125,9 @@ $.ReferenceStrip = function ( options ) { scrollHandler: $.delegate( this, onStripScroll ), enterHandler: $.delegate( this, onStripEnter ), exitHandler: $.delegate( this, onStripExit ), + keyDownHandler: $.delegate( this, onKeyDown ), keyHandler: $.delegate( this, onKeyPress ) - } ).setTracking( true ); + } ); //Controls the position and orientation of the reference strip and sets the //appropriate width and height @@ -214,7 +215,7 @@ $.ReferenceStrip = function ( options ) { viewer.goToPage( page ); } } - } ).setTracking( true ); + } ); this.element.appendChild( element ); @@ -446,8 +447,10 @@ function loadPanels( strip, viewerSize, scroll ) { style.width = ( strip.panelWidth - 4 ) + 'px'; style.height = ( strip.panelHeight - 4 ) + 'px'; + // TODO: What is this for? Future keyboard navigation support? miniViewer.displayRegion.innerTracker = new $.MouseTracker( { - element: miniViewer.displayRegion + element: miniViewer.displayRegion, + startDisabled: true } ); element.getElementsByTagName( 'div' )[0].appendChild( @@ -511,6 +514,37 @@ function onStripExit( event ) { } +/** + * @private + * @inner + * @function + */ +function onKeyDown( event ) { + //console.log( event.keyCode ); + + if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) { + switch ( event.keyCode ) { + case 38: //up arrow + onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); + return false; + case 40: //down arrow + onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); + return false; + case 37: //left arrow + onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); + return false; + case 39: //right arrow + onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); + return false; + default: + //console.log( 'navigator keycode %s', event.keyCode ); + return true; + } + } else { + return true; + } +} + /** * @private @@ -520,35 +554,35 @@ function onStripExit( event ) { function onKeyPress( event ) { //console.log( event.keyCode ); - switch ( event.keyCode ) { - case 61: //=|+ - onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); - return false; - case 45: //-|_ - onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); - return false; - case 48: //0|) - case 119: //w - case 87: //W - case 38: //up arrow - onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); - return false; - case 115: //s - case 83: //S - case 40: //down arrow - onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); - return false; - case 97: //a - case 37: //left arrow - onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); - return false; - case 100: //d - case 39: //right arrow - onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); - return false; - default: - //console.log( 'navigator keycode %s', event.keyCode ); - return true; + if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) { + switch ( event.keyCode ) { + case 61: //=|+ + onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); + return false; + case 45: //-|_ + onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); + return false; + case 48: //0|) + case 119: //w + case 87: //W + onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); + return false; + case 115: //s + case 83: //S + onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); + return false; + case 97: //a + onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); + return false; + case 100: //d + onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); + return false; + default: + //console.log( 'navigator keycode %s', event.keyCode ); + return true; + } + } else { + return true; } } diff --git a/src/viewer.js b/src/viewer.js index 39ed6294..85ec594a 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -108,14 +108,6 @@ $.Viewer = function( options ) { * @memberof OpenSeadragon.Viewer# */ container: null, - /** - * A <textarea> element, the element where keyboard events are handled.

- * Child element of {@link OpenSeadragon.Viewer#container}, - * positioned below {@link OpenSeadragon.Viewer#canvas}. - * @member {Element} keyboardCommandArea - * @memberof OpenSeadragon.Viewer# - */ - keyboardCommandArea: null, /** * A <div> element, the element where user-input events are handled for panning and zooming.

* Child element of {@link OpenSeadragon.Viewer#container}, @@ -263,7 +255,6 @@ $.Viewer = function( options ) { this.element = this.element || document.getElementById( this.id ); this.canvas = $.makeNeutralElement( "div" ); - this.keyboardCommandArea = $.makeNeutralElement( "textarea" ); this.drawersContainer = $.makeNeutralElement( "div" ); this.overlaysContainer = $.makeNeutralElement( "div" ); @@ -277,6 +268,7 @@ $.Viewer = function( options ) { style.left = "0px"; }(this.canvas.style)); $.setElementTouchActionNone( this.canvas ); + this.canvas.tabIndex = options.tabIndex || 0; //the container is created through applying the ControlDock constructor above this.container.className = "openseadragon-container"; @@ -290,19 +282,7 @@ $.Viewer = function( options ) { style.textAlign = "left"; // needed to protect against }( this.container.style )); - this.keyboardCommandArea.className = "keyboard-command-area"; - (function( style ){ - style.width = "100%"; - style.height = "100%"; - style.overflow = "hidden"; - style.position = "absolute"; - style.top = "0px"; - style.left = "0px"; - style.resize = "none"; - }( this.keyboardCommandArea.style )); - this.container.insertBefore( this.canvas, this.container.firstChild ); - this.container.insertBefore( this.keyboardCommandArea, this.container.firstChild ); this.element.appendChild( this.container ); this.canvas.appendChild( this.drawersContainer ); this.canvas.appendChild( this.overlaysContainer ); @@ -315,96 +295,39 @@ $.Viewer = function( options ) { this.bodyOverflow = document.body.style.overflow; this.docOverflow = document.documentElement.style.overflow; - this.keyboardCommandArea.innerTracker = new $.MouseTracker({ - _this : this, - element: this.keyboardCommandArea, - focusHandler: function( event ){ - if ( !event.preventDefaultAction ) { - var point = $.getElementPosition( this.element ); - window.scrollTo( 0, point.y ); - } - }, - - keyHandler: function( event ){ - if ( !event.preventDefaultAction ) { - 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 ) { - _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 - - this.innerTracker = new $.MouseTracker({ - element: this.canvas, - clickTimeThreshold: this.clickTimeThreshold, - clickDistThreshold: this.clickDistThreshold, - dblClickTimeThreshold: this.dblClickTimeThreshold, - dblClickDistThreshold: this.dblClickDistThreshold, - clickHandler: $.delegate( this, onCanvasClick ), - dblClickHandler: $.delegate( this, onCanvasDblClick ), - dragHandler: $.delegate( this, onCanvasDrag ), - dragEndHandler: $.delegate( this, onCanvasDragEnd ), - releaseHandler: $.delegate( this, onCanvasRelease ), - scrollHandler: $.delegate( this, onCanvasScroll ), - pinchHandler: $.delegate( this, onCanvasPinch ) - }).setTracking( this.mouseNavEnabled ? true : false ); // default state + element: this.canvas, + startDisabled: this.mouseNavEnabled ? false : true, + clickTimeThreshold: this.clickTimeThreshold, + clickDistThreshold: this.clickDistThreshold, + dblClickTimeThreshold: this.dblClickTimeThreshold, + dblClickDistThreshold: this.dblClickDistThreshold, + 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 ), + exitHandler: $.delegate( this, onCanvasExit ), + 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({ element: this.container, + startDisabled: this.mouseNavEnabled ? false : true, clickTimeThreshold: this.clickTimeThreshold, clickDistThreshold: this.clickDistThreshold, dblClickTimeThreshold: this.dblClickTimeThreshold, dblClickDistThreshold: this.dblClickDistThreshold, enterHandler: $.delegate( this, onContainerEnter ), - exitHandler: $.delegate( this, onContainerExit ), - pressHandler: $.delegate( this, onContainerPress ), - releaseHandler: $.delegate( this, onContainerRelease ) - }).setTracking( this.mouseNavEnabled ? true : false ); // always tracking + exitHandler: $.delegate( this, onContainerExit ) + }); if( this.toolbar ){ this.toolbar = new $.ControlDock({ element: this.toolbar }); @@ -539,9 +462,13 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, this.navigator.close(); } - this.clearOverlays(); + if( ! this.preserveOverlays) + { + this.clearOverlays(); + this.overlaysContainer.innerHTML = ""; + } + this.drawersContainer.innerHTML = ""; - this.overlaysContainer.innerHTML = ""; if ( this.drawer ) { this.drawer.destroy(); @@ -605,9 +532,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, } // destroy the mouse trackers - if (this.keyboardCommandArea){ - this.keyboardCommandArea.innerTracker.destroy(); - } if (this.innerTracker){ this.innerTracker.destroy(); } @@ -620,7 +544,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, // clear all our references to dom objects this.canvas = null; - this.keyboardCommandArea = null; this.container = null; // clear our reference to the main element - they will need to pass it in again, creating a new viewer @@ -1968,6 +1891,10 @@ function openTileSource( viewer, source ) { _this.viewport.resetContentSize( _this.source.dimensions ); } + if( _this.preserveOverlays ){ + _this.overlays = _this.currentOverlays; + } + _this.source.overlays = _this.source.overlays || []; _this.drawer = new $.Drawer({ @@ -2262,9 +2189,102 @@ function onBlur(){ } +function onCanvasKeyDown( event ) { + if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) { + switch( event.keyCode ){ + 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 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 37://left arrow + this.viewport.panBy(new $.Point(-0.05, 0)); + this.viewport.applyConstraints(); + return false; + 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; + } + } else { + return true; + } +} + +function onCanvasKeyPress( event ) { + if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) { + 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 + 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 + 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 + this.viewport.panBy(new $.Point(-0.05, 0)); + this.viewport.applyConstraints(); + return false; + case 100://d + this.viewport.panBy(new $.Point(0.05, 0)); + this.viewport.applyConstraints(); + return false; + default: + //console.log( 'navigator keycode %s', event.keyCode ); + return true; + } + } else { + return true; + } +} + 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 ( !event.preventDefaultAction && this.viewport && event.quick ) { gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); if ( gestureSettings.clickToZoom ) { @@ -2420,15 +2440,102 @@ function onCanvasDragEnd( event ) { }); } +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. Deprecated. Use buttons instead. + * @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 onCanvasExit( 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. Deprecated. Use buttons instead. + * @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 mouse button is released or touch ends on the {@link OpenSeadragon.Viewer#canvas} element. + * 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. @@ -2437,6 +2544,7 @@ function onCanvasRelease( event ) { */ this.raiseEvent( 'canvas-release', { tracker: event.eventSource, + pointerType: event.pointerType, position: event.position, insideElementPressed: event.insideElementPressed, insideElementReleased: event.insideElementReleased, @@ -2444,6 +2552,62 @@ function onCanvasRelease( event ) { }); } +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, @@ -2546,8 +2710,38 @@ function onCanvasScroll( event ) { return false; } +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 {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. Deprecated. Use buttons instead. + * @property {Object} originalEvent - The original DOM event. + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + this.raiseEvent( 'container-enter', { + tracker: event.eventSource, + position: event.position, + buttons: event.buttons, + pointers: event.pointers, + insideElementPressed: event.insideElementPressed, + buttonDownAny: event.buttonDownAny, + originalEvent: event.originalEvent + }); +} + function onContainerExit( event ) { - if ( !event.insideElementPressed ) { + if ( event.pointers < 1 ) { THIS[ this.hash ].mouseInside = false; if ( !THIS[ this.hash ].animating ) { beginControlsAutoHide( this ); @@ -2563,6 +2757,7 @@ function onContainerExit( 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} 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. Deprecated. Use buttons instead. * @property {Object} originalEvent - The original DOM event. @@ -2572,71 +2767,7 @@ function onContainerExit( event ) { tracker: event.eventSource, position: event.position, buttons: event.buttons, - insideElementPressed: event.insideElementPressed, - buttonDownAny: event.buttonDownAny, - originalEvent: event.originalEvent - }); -} - -function onContainerPress( event ) { - if ( event.pointerType === 'touch' && !$.MouseTracker.haveTouchEnter ) { - THIS[ this.hash ].mouseInside = true; - abortControlsAutoHide( this ); - } -} - -function onContainerRelease( event ) { - if ( !event.insideElementReleased || ( event.pointerType === 'touch' && !$.MouseTracker.haveTouchEnter ) ) { - THIS[ this.hash ].mouseInside = false; - if ( !THIS[ this.hash ].animating ) { - beginControlsAutoHide( this ); - } - } - /** - * Raised when the mouse button is released or touch ends on the {@link OpenSeadragon.Viewer#container} element. - * - * @event container-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 {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( 'container-release', { - tracker: event.eventSource, - position: event.position, - insideElementPressed: event.insideElementPressed, - insideElementReleased: event.insideElementReleased, - originalEvent: event.originalEvent - }); -} - -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 {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 {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. Deprecated. Use buttons instead. - * @property {Object} originalEvent - The original DOM event. - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - this.raiseEvent( 'container-enter', { - tracker: event.eventSource, - position: event.position, - buttons: event.buttons, + pointers: event.pointers, insideElementPressed: event.insideElementPressed, buttonDownAny: event.buttonDownAny, originalEvent: event.originalEvent diff --git a/test/events.js b/test/events.js index ba1a36e2..677e805f 100644 --- a/test/events.js +++ b/test/events.js @@ -35,6 +35,8 @@ origExitHandler, origPressHandler, origReleaseHandler, + origNonPrimaryPressHandler, + origNonPrimaryReleaseHandler, origMoveHandler, origClickHandler, origDblClickHandler, @@ -44,6 +46,10 @@ exitCount, pressCount, releaseCount, + rightPressCount, + rightReleaseCount, + middlePressCount, + middleReleaseCount, moveCount, clickCount, dblClickCount, @@ -94,6 +100,36 @@ return true; } }; + origNonPrimaryPressHandler = tracker.nonPrimaryPressHandler; + tracker.nonPrimaryPressHandler = function ( event ) { + if (event.button === 0) { + pressCount++; + } else if (event.button === 1) { + middlePressCount++; + } else if (event.button === 2) { + rightPressCount++; + } + if (origNonPrimaryPressHandler) { + return origNonPrimaryPressHandler( event ); + } else { + return true; + } + }; + origNonPrimaryReleaseHandler = tracker.nonPrimaryReleaseHandler; + tracker.nonPrimaryReleaseHandler = function ( event ) { + if (event.button === 0) { + releaseCount++; + } else if (event.button === 1) { + middleReleaseCount++; + } else if (event.button === 2) { + rightReleaseCount++; + } + if (origNonPrimaryReleaseHandler) { + return origNonPrimaryReleaseHandler( event ); + } else { + return true; + } + }; origMoveHandler = tracker.moveHandler; tracker.moveHandler = function ( event ) { moveCount++; @@ -168,20 +204,37 @@ simEvent.relatedTarget = document.body; $canvas.simulate( OpenSeadragon.MouseTracker.haveMouseEnter ? 'mouseleave' : 'mouseout', simEvent ); }; - var simulateLeaveFrame = function (x, y) { - simEvent.clientX = offset.left + x; - simEvent.clientY = offset.top + y; - simEvent.relatedTarget = document.getElementsByTagName("html")[0]; - $canvas.simulate( OpenSeadragon.MouseTracker.haveMouseEnter ? 'mouseleave' : 'mouseout', simEvent ); - }; + + //var simulateLeaveFrame = function (x, y) { + // simEvent.clientX = offset.left + x; + // simEvent.clientY = offset.top + y; + // simEvent.relatedTarget = document.getElementsByTagName("html")[0]; + // $canvas.simulate( OpenSeadragon.MouseTracker.haveMouseEnter ? 'mouseleave' : 'mouseout', simEvent ); + //}; var simulateDown = function (x, y) { + simEvent.button = 0; simEvent.clientX = offset.left + x; simEvent.clientY = offset.top + y; $canvas.simulate( 'mousedown', simEvent ); }; var simulateUp = function (x, y) { + simEvent.button = 0; + simEvent.clientX = offset.left + x; + simEvent.clientY = offset.top + y; + $canvas.simulate( 'mouseup', simEvent ); + }; + + var simulateNonPrimaryDown = function (x, y, button) { + simEvent.button = button; + simEvent.clientX = offset.left + x; + simEvent.clientY = offset.top + y; + $canvas.simulate( 'mousedown', simEvent ); + }; + + var simulateNonPrimaryUp = function (x, y, button) { + simEvent.button = button; simEvent.clientX = offset.left + x; simEvent.clientY = offset.top + y; $canvas.simulate( 'mouseup', simEvent ); @@ -198,6 +251,7 @@ var resetForAssessment = function () { simEvent = { + button: 0, clientX: offset.left, clientY: offset.top }; @@ -205,6 +259,10 @@ exitCount = 0; pressCount = 0; releaseCount = 0; + rightPressCount = 0; + rightReleaseCount = 0; + middlePressCount = 0; + middleReleaseCount = 0; moveCount = 0; clickCount = 0; dblClickCount = 0; @@ -231,6 +289,18 @@ if ('releaseCount' in expected) { equal( releaseCount, expected.releaseCount, expected.description + 'releaseHandler event count matches expected (' + expected.releaseCount + ')' ); } + if ('rightPressCount' in expected) { + equal( rightPressCount, expected.rightPressCount, expected.description + 'nonPrimaryPressHandler event count (secondary/right button) matches expected (' + expected.rightPressCount + ')' ); + } + if ('rightReleaseCount' in expected) { + equal( rightReleaseCount, expected.rightReleaseCount, expected.description + 'nonPrimaryReleaseHandler event count (secondary/right button) matches expected (' + expected.rightReleaseCount + ')' ); + } + if ('middlePressCount' in expected) { + equal( middlePressCount, expected.middlePressCount, expected.description + 'nonPrimaryPressHandler event count (aux/middle button) matches expected (' + expected.middlePressCount + ')' ); + } + if ('middleReleaseCount' in expected) { + equal( middleReleaseCount, expected.middleReleaseCount, expected.description + 'nonPrimaryReleaseHandler event count (aux/middle button) matches expected (' + expected.middleReleaseCount + ')' ); + } if ('moveCount' in expected) { equal( moveCount, expected.moveCount, expected.description + 'moveHandler event count matches expected (' + expected.moveCount + ')' ); } @@ -290,6 +360,10 @@ exitCount: 0, pressCount: 0, releaseCount: 1, + rightPressCount: 0, + rightReleaseCount: 0, + middlePressCount: 0, + middleReleaseCount: 0, moveCount: 20, clickCount: 0, dblClickCount: 0, @@ -315,6 +389,10 @@ exitCount: 1, pressCount: 0, releaseCount: 0, + rightPressCount: 0, + rightReleaseCount: 0, + middlePressCount: 0, + middleReleaseCount: 0, moveCount: 20, clickCount: 0, dblClickCount: 0, @@ -338,6 +416,10 @@ exitCount: 1, pressCount: 0, releaseCount: 0, + rightPressCount: 0, + rightReleaseCount: 0, + middlePressCount: 0, + middleReleaseCount: 0, moveCount: 20, clickCount: 0, dblClickCount: 0, @@ -350,7 +432,7 @@ //quickClick: false }); - // enter-press-release-press-release-exit (double click) + // enter-press-release-press-release-exit (primary/left double click) resetForAssessment(); simulateEnter(0, 0); simulateDown(0, 0); @@ -359,11 +441,15 @@ simulateUp(0, 0); simulateLeave(-1, -1); assessGestureExpectations({ - description: 'enter-press-release-press-release-exit (double click): ', + description: 'enter-press-release-press-release-exit (primary/left double click): ', enterCount: 1, exitCount: 1, pressCount: 2, releaseCount: 2, + rightPressCount: 0, + rightReleaseCount: 0, + middlePressCount: 0, + middleReleaseCount: 0, moveCount: 0, clickCount: 2, dblClickCount: 1, @@ -376,18 +462,22 @@ //quickClick: true }); - // enter-press-release-exit (click) + // enter-press-release-exit (primary/left click) resetForAssessment(); simulateEnter(0, 0); simulateDown(0, 0); simulateUp(0, 0); simulateLeave(-1, -1); assessGestureExpectations({ - description: 'enter-press-release-exit (click): ', + description: 'enter-press-release-exit (primary/left click): ', enterCount: 1, exitCount: 1, pressCount: 1, releaseCount: 1, + rightPressCount: 0, + rightReleaseCount: 0, + middlePressCount: 0, + middleReleaseCount: 0, moveCount: 0, clickCount: 1, dblClickCount: 0, @@ -400,6 +490,92 @@ quickClick: true }); + // enter-nonprimarypress-nonprimaryrelease-exit (secondary/right click) + resetForAssessment(); + simulateEnter(0, 0); + simulateNonPrimaryDown(0, 0, 2); + simulateNonPrimaryUp(0, 0, 2); + simulateLeave(-1, -1); + assessGestureExpectations({ + description: 'enter-nonprimarypress-nonprimaryrelease-exit (secondary/right click): ', + enterCount: 1, + exitCount: 1, + pressCount: 0, + releaseCount: 0, + rightPressCount: 1, + rightReleaseCount: 1, + middlePressCount: 0, + middleReleaseCount: 0, + moveCount: 0, + clickCount: 0, + dblClickCount: 0, + dragCount: 0, + dragEndCount: 0, + //insideElementPressed: true, + //insideElementReleased: true, + contacts: 0, + trackedPointers: 0, + //quickClick: true + }); + + // enter-nonprimarypress-nonprimaryrelease-exit (aux/middle click) + resetForAssessment(); + simulateEnter(0, 0); + simulateNonPrimaryDown(0, 0, 1); + simulateNonPrimaryUp(0, 0, 1); + simulateLeave(-1, -1); + assessGestureExpectations({ + description: 'enter-nonprimarypress-nonprimaryrelease-exit (aux/middle click): ', + enterCount: 1, + exitCount: 1, + pressCount: 0, + releaseCount: 0, + rightPressCount: 0, + rightReleaseCount: 0, + middlePressCount: 1, + middleReleaseCount: 1, + moveCount: 0, + clickCount: 0, + dblClickCount: 0, + dragCount: 0, + dragEndCount: 0, + //insideElementPressed: true, + //insideElementReleased: true, + contacts: 0, + trackedPointers: 0, + //quickClick: true + }); + + // enter-nonprimarypress-move-nonprimaryrelease-move-exit (secondary/right button drag, release in tracked element) + resetForAssessment(); + simulateEnter(0, 0); + simulateNonPrimaryDown(0, 0, 2); + simulateMove(1, 1, 100); + simulateNonPrimaryUp(10, 10, 2); + simulateMove(-1, -1, 100); + simulateLeave(-1, -1); + assessGestureExpectations({ + description: 'enter-nonprimarypress-move-nonprimaryrelease-move-exit (secondary/right button drag, release in tracked element): ', + enterCount: 1, + exitCount: 1, + pressCount: 0, + releaseCount: 0, + rightPressCount: 1, + rightReleaseCount: 1, + middlePressCount: 0, + middleReleaseCount: 0, + moveCount: 200, + clickCount: 0, + dblClickCount: 0, + dragCount: 0, + dragEndCount: 0, + //insideElementPressed: true, + //insideElementReleased: true, + contacts: 0, + trackedPointers: 0, + //quickClick: false + }); + // enter-press-move-release-move-exit (drag, release in tracked element) resetForAssessment(); simulateEnter(0, 0); @@ -414,6 +590,10 @@ exitCount: 1, pressCount: 1, releaseCount: 1, + rightPressCount: 0, + rightReleaseCount: 0, + middlePressCount: 0, + middleReleaseCount: 0, moveCount: 200, clickCount: 1, dblClickCount: 0, @@ -441,6 +621,10 @@ exitCount: 1, pressCount: 1, releaseCount: 1, + rightPressCount: 0, + rightReleaseCount: 0, + middlePressCount: 0, + middleReleaseCount: 0, moveCount: 15, clickCount: 0, dblClickCount: 0, @@ -453,32 +637,36 @@ quickClick: false }); + //// enter-press-move-exit-move-release-outside (drag, release outside iframe) + //resetForAssessment(); + //simulateEnter(0, 0); + //simulateDown(0, 0); + //simulateMove(1, 1, 5); + //simulateMove(-1, -1, 5); + //simulateLeaveFrame(-1, -1); + //// you don't actually receive the mouseup if you mouseup outside of the document + //assessGestureExpectations({ + // description: 'enter-press-move-exit-move-release-outside (drag, release outside iframe): ', + // enterCount: 1, + // exitCount: 1, + // pressCount: 1, + // releaseCount: 1, + // rightPressCount: 0, + // rightReleaseCount: 0, + // middlePressCount: 0, + // middleReleaseCount: 0, + // moveCount: 10, + // clickCount: 0, + // dblClickCount: 0, + // dragCount: 10, + // dragEndCount: 1, + // insideElementPressed: true, + // insideElementReleased: false, + // contacts: 0, + // trackedPointers: 0, + // quickClick: false + //}); - // enter-press-move-exit-move-release-outside (drag, release outside iframe) - resetForAssessment(); - simulateEnter(0, 0); - simulateDown(0, 0); - simulateMove(1, 1, 5); - simulateMove(-1, -1, 5); - simulateLeaveFrame(-1, -1); - // you don't actually receive the mouseup if you mouseup outside of the document - assessGestureExpectations({ - description: 'enter-press-move-exit-move-release-outside (drag, release outside iframe): ', - enterCount: 1, - exitCount: 1, - pressCount: 1, - releaseCount: 1, - moveCount: 10, - clickCount: 0, - dblClickCount: 0, - dragCount: 10, - dragEndCount: 1, - insideElementPressed: true, - insideElementReleased: false, - contacts: 0, - trackedPointers: 0, - quickClick: false - }); unhookViewerHandlers(); viewer.close(); @@ -602,7 +790,7 @@ releaseHandler: onMouseTrackerRelease, clickHandler: onMouseTrackerClick, exitHandler: onMouseTrackerExit - } ).setTracking( true ); + } ); var event = { clientX:1, diff --git a/test/legacy.mouse.shim.js b/test/legacy.mouse.shim.js index 7a8ff7cc..3609ed85 100644 --- a/test/legacy.mouse.shim.js +++ b/test/legacy.mouse.shim.js @@ -11,26 +11,29 @@ $.MouseTracker.subscribeEvents.push( "MozMousePixelScroll" ); } - $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout", "mousedown", "mouseup", "mousemove" ); - $.MouseTracker.haveMouseEnter = false; - if ( 'ontouchstart' in window ) { - // iOS, Android, and other W3c Touch Event implementations (see http://www.w3.org/TR/2011/WD-touch-events-20110505) - $.MouseTracker.subscribeEvents.push( "touchstart", "touchend", "touchmove", "touchcancel" ); - if ( 'ontouchenter' in window ) { - $.MouseTracker.subscribeEvents.push( "touchenter", "touchleave" ); - $.MouseTracker.haveTouchEnter = true; - } else { - $.MouseTracker.haveTouchEnter = false; - } + $.MouseTracker.havePointerEvents = false; + if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { + $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" ); + $.MouseTracker.haveMouseEnter = true; } else { - $.MouseTracker.haveTouchEnter = false; + $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout" ); + $.MouseTracker.haveMouseEnter = false; + } + $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" ); + if ( 'ontouchstart' in window ) { + // iOS, Android, and other W3c Touch Event implementations + // (see http://www.w3.org/TR/touch-events/) + // (see https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html) + // (see https://developer.apple.com/library/safari/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html) + $.MouseTracker.subscribeEvents.push( "touchstart", "touchend", "touchmove", "touchcancel" ); } if ( 'ongesturestart' in window ) { - // iOS (see https://developer.apple.com/library/safari/documentation/UserExperience/Reference/GestureEventClassReference/GestureEvent/GestureEvent.html) + // iOS (see https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html) // Subscribe to these to prevent default gesture handling $.MouseTracker.subscribeEvents.push( "gesturestart", "gesturechange" ); } $.MouseTracker.mousePointerId = "legacy-mouse"; $.MouseTracker.maxTouchPoints = 10; + }(OpenSeadragon));