Merge pull request #545 from openseadragon/bugs

Various collections fixes
This commit is contained in:
Ian Gilman 2015-01-02 15:10:14 -08:00
commit 56cd480736
12 changed files with 328 additions and 100 deletions

View File

@ -38,6 +38,7 @@ OPENSEADRAGON CHANGELOG
* Margins option to push the home region in from the edges of the Viewer (#505)
* Rect and Point toString() functions are now consistent: rounding values to nearest hundredth
* Overlays appear in the DOM immediately on open or addOverlay (#507)
* imageLoaderLimit now works (#544)
1.2.0: (in progress)
@ -58,6 +59,7 @@ OPENSEADRAGON CHANGELOG
* Added option for home button to fill viewer (#474)
* 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:

View File

@ -170,8 +170,6 @@
* @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 ); },
@ -197,7 +195,9 @@
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 ); },
@ -227,12 +227,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,
@ -944,6 +938,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 */{
/**
@ -1042,33 +1042,47 @@
i,
pointerListCount = delegate.activePointersLists.length;
if ( delegate.pointerCaptureCount > 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
);
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.pointerCaptureCount = 0;
delegate.activePointersLists[ i ].captureCount = 0;
}
}
for ( i = 0; i < pointerListCount; i++ ) {
@ -1130,29 +1144,62 @@
}
}
/**
* @private
* @inner
*/
function getCaptureEventParams( tracker, pointerType ) {
var delegate = THIS[ tracker.hash ];
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 {
return {
upName: $.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp',
upHandler: delegate.pointerupcaptured,
moveName: $.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove',
moveHandler: delegate.pointermovecaptured
};
}
}
/**
* Begin capturing pointer events to the tracked element.
* @private
* @inner
*/
function capturePointer( tracker, isLegacyMouse ) {
var delegate = THIS[ tracker.hash ];
function capturePointer( tracker, pointerType ) {
var delegate = THIS[ tracker.hash ],
pointsList = tracker.getActivePointersListByType( pointerType ),
eventParams = getCaptureEventParams( tracker, pointerType );
delegate.pointerCaptureCount++;
pointsList.captureCount++;
if ( delegate.pointerCaptureCount === 1 ) {
if ( pointsList.captureCount === 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(
$.MouseTracker.captureElement,
isLegacyMouse ? 'mouseup' : ($.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp'),
isLegacyMouse ? delegate.mouseupcaptured : delegate.pointerupcaptured,
eventParams.upName,
eventParams.upHandler,
true
);
$.addEvent(
$.MouseTracker.captureElement,
isLegacyMouse ? 'mousemove' : ($.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove'),
isLegacyMouse ? delegate.mousemovecaptured : delegate.pointermovecaptured,
eventParams.moveName,
eventParams.moveHandler,
true
);
}
@ -1164,24 +1211,26 @@
* @private
* @inner
*/
function releasePointer( tracker, isLegacyMouse ) {
var delegate = THIS[ tracker.hash ];
function releasePointer( tracker, pointerType ) {
var delegate = THIS[ tracker.hash ],
pointsList = tracker.getActivePointersListByType( pointerType ),
eventParams = getCaptureEventParams( tracker, pointerType );
delegate.pointerCaptureCount--;
pointsList.captureCount--;
if ( delegate.pointerCaptureCount === 0 ) {
if ( pointsList.captureCount === 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(
$.MouseTracker.captureElement,
isLegacyMouse ? 'mousemove' : ($.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove'),
isLegacyMouse ? delegate.mousemovecaptured : delegate.pointermovecaptured,
eventParams.moveName,
eventParams.moveHandler,
true
);
$.removeEvent(
$.MouseTracker.captureElement,
isLegacyMouse ? 'mouseup' : ($.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp'),
isLegacyMouse ? delegate.mouseupcaptured : delegate.pointerupcaptured,
eventParams.upName,
eventParams.upHandler,
true
);
}
@ -1526,7 +1575,7 @@
if ( updatePointersDown( tracker, event, [ gPoint ], event.button ) ) {
$.stopEvent( event );
capturePointer( tracker, true );
capturePointer( tracker, 'mouse' );
}
if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler ) {
@ -1574,7 +1623,7 @@
};
if ( updatePointersUp( tracker, event, [ gPoint ], event.button ) ) {
releasePointer( tracker, true );
releasePointer( tracker, 'mouse' );
}
}
@ -1695,8 +1744,8 @@
}
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 );
@ -1708,6 +1757,28 @@
* @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,
touchCount = event.changedTouches.length,
@ -1725,9 +1796,9 @@
} );
}
// 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 ) {
@ -1743,6 +1814,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 = [];
@ -1866,8 +1959,8 @@
};
if ( updatePointersDown( tracker, event, [ gPoint ], event.button ) ) {
capturePointer( tracker, false );
$.stopEvent( event );
capturePointer( tracker, 'pointer' );
}
if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
@ -1917,8 +2010,7 @@
};
if ( updatePointersUp( tracker, event, [ gPoint ], event.button ) ) {
releasePointer( tracker, false );
//$.stopEvent( event );
releasePointer( tracker, 'pointer' );
}
}

View File

@ -245,14 +245,10 @@ $.Navigator = function( options ){
});
viewer.world.addHandler("remove-item", function(event) {
var count = _this.world.getItemCount();
var item;
for (var i = 0; i < count; i++) {
item = _this.world.getItemAt(i);
if (item._originalForNavigator === event.item) {
_this.world.removeItem(item);
break;
}
var theirItem = event.item;
var myItem = _this._getMatchingItem(theirItem);
if (myItem) {
_this.world.removeItem(myItem);
}
});
@ -343,16 +339,45 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
// overrides Viewer.addTiledImage
addTiledImage: function(options) {
var _this = this;
var original = options.originalTiledImage;
delete options.original;
var optionsClone = $.extend({}, options, {
success: function(event) {
event.item._originalForNavigator = original;
var myItem = event.item;
myItem._originalForNavigator = original;
_this._matchBounds(myItem, original, true);
original.addHandler('bounds-change', function() {
_this._matchBounds(myItem, original);
});
}
});
return $.Viewer.prototype.addTiledImage.apply(this, [optionsClone]);
},
// private
_getMatchingItem: function(theirItem) {
var count = this.world.getItemCount();
var item;
for (var i = 0; i < count; i++) {
item = this.world.getItemAt(i);
if (item._originalForNavigator === theirItem) {
return item;
}
}
return null;
},
// private
_matchBounds: function(myItem, theirItem, immediately) {
var bounds = theirItem.getBounds();
myItem.setPosition(bounds.getTopLeft(), immediately);
myItem.setWidth(bounds.width, immediately);
}
});

View File

@ -36,6 +36,7 @@
// dictionary from hash to private properties
var THIS = {};
var nextHash = 1;
/**
*
@ -89,7 +90,7 @@ $.Viewer = function( options ) {
//internal state and dom identifiers
id: options.id,
hash: options.hash || options.id,
hash: options.hash || nextHash++,
//dom nodes
/**
@ -498,7 +499,7 @@ $.Viewer = function( options ) {
}
// Open initial tilesources
if ( this.tileSources && this.tileSources.length) {
if (this.tileSources) {
this.open( this.tileSources );
}

View File

@ -230,7 +230,16 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* @function
*/
getHomeBounds: function() {
return this.homeBounds.clone();
var center = this.homeBounds.getCenter( ),
width = 1.0 / this.getHomeZoom( ),
height = width / this.getAspectRatio();
return new $.Rect(
center.x - ( width / 2.0 ),
center.y - ( height / 2.0 ),
width,
height
);
},
/**

View File

@ -104,7 +104,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
* @returns {OpenSeadragon.TiledImage} The item at the specified index.
*/
getItemAt: function( index ) {
$.console.assert(index !== 'undefined', "[World.getItemAt] index is required");
$.console.assert(index !== undefined, "[World.getItemAt] index is required");
return this._items[ index ];
},
@ -133,7 +133,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
*/
setItemIndex: function( item, index ) {
$.console.assert(item, "[World.setItemIndex] item is required");
$.console.assert(index !== 'undefined', "[World.setItemIndex] index is required");
$.console.assert(index !== undefined, "[World.setItemIndex] index is required");
var oldIndex = this.getIndexOfItem( item );

View File

@ -65,7 +65,7 @@
<script src="/test/modules/multi-image.js"></script>
<script src="/test/modules/overlays.js"></script>
<script src="/test/modules/controls.js"></script>
<script src="/test/viewport.js"></script>
<script src="/test/modules/viewport.js"></script>
<script src="/test/modules/world.js"></script>
<script src="/test/modules/drawer.js"></script>
<script src="/test/modules/tiledimage.js"></script>

View File

@ -17,13 +17,15 @@
zoomPerScroll: 1.02,
showNavigator: testNavigator,
useCanvas: true,
// defaultZoomLevel: 2,
// homeFillsViewer: true,
// sequenceMode: true,
// showReferenceStrip: true,
// referenceStripScroll: 'vertical',
navPrevNextWrap: false,
preserveViewport: false,
collectionMode: true,
collectionRows: 1,
// collectionMode: true,
// collectionRows: 1,
// collectionLayout: 'vertical',
// collectionTileSize: 10,
// collectionTileMargin: 10,
@ -33,6 +35,20 @@
prefixUrl: "../../../build/openseadragon/images/"
};
var highsmith = {
Image: {
xmlns: "http://schemas.microsoft.com/deepzoom/2008",
Url: "http://openseadragon.github.io/example-images/highsmith/highsmith_files/",
Format: "jpg",
Overlap: "2",
TileSize: "256",
Size: {
Height: "9221",
Width: "7026"
}
}
};
if (testInitialOpen) {
config.tileSources = [
{
@ -55,6 +71,11 @@
height: 1
}
];
// config.tileSources = {
// tileSource: highsmith,
// width: 1
// };
}
if (testOverlays) {

View File

@ -14,12 +14,18 @@
margin: 0;
}
.controls {
position: absolute;
right: 10px;
top: 10px;
}
</style>
<script>
// ----------
App = {
itemCount: 20,
itemCount: 5,
positionRange: 100,
sizeRange: 30,
delay: 100,
@ -29,6 +35,7 @@
var self = this;
this.index = 0;
this.paused = false;
var tileSource = {
Image: {
@ -56,29 +63,57 @@
});
this.viewer.addHandler('open', function() {
self.updateCount();
self.viewer.viewport.fitBounds(new OpenSeadragon.Rect(0, 0,
self.positionRange + self.sizeRange,
self.positionRange + self.sizeRange));
self.animate();
});
var $pause = $('.toggle-pause').click(function() {
self.paused = !self.paused;
$pause.text(self.paused ? 'Play' : 'Pause');
});
$('.add').click(function() {
for (var i = 0; i < self.itemCount; i++) {
self.viewer.addTiledImage({
tileSource: tileSource,
x: Math.random() * self.positionRange,
y: Math.random() * self.positionRange,
width: Math.random() * self.sizeRange,
success: function() {
self.updateCount();
}
});
}
});
},
// ----------
animate: function() {
var self = this;
var item = this.viewer.world.getItemAt(this.index);
item.setPosition(new OpenSeadragon.Point(Math.random() * this.positionRange,
Math.random() * this.positionRange));
if (!this.paused) {
var item = this.viewer.world.getItemAt(this.index);
item.setPosition(new OpenSeadragon.Point(Math.random() * this.positionRange,
Math.random() * this.positionRange));
item.setWidth(Math.random() * this.sizeRange);
item.setWidth(Math.random() * this.sizeRange);
this.index = (this.index + 1) % this.viewer.world.getItemCount();
this.index = (this.index + 1) % this.viewer.world.getItemCount();
}
setTimeout(function() {
self.animate();
}, this.delay);
},
// ----------
updateCount: function() {
$('.count').text(this.viewer.world.getItemCount());
}
};
@ -91,5 +126,10 @@
</head>
<body>
<div id="contentDiv" class="openseadragon1"></div>
<div class="controls">
<button class="toggle-pause">Pause</button>
<button class="add">Add More</button>
Image Count: <span class="count"></span>
</div>
</body>
</html>

View File

@ -1,4 +1,4 @@
/* global QUnit, module, Util, $, console, test, asyncTest, start, ok, equal */
/* global QUnit, module, Util, $, console, test, asyncTest, start, ok, equal, propEqual */
(function () {
var debug = false,
@ -799,4 +799,48 @@
viewer.addHandler('open', openHandler1);
});
asyncTest('Item positions including collection mode', function() {
var navAddCount = 0;
viewer = OpenSeadragon({
id: 'example',
prefixUrl: '/build/openseadragon/images/',
tileSources: ['/test/data/testpattern.dzi', '/test/data/testpattern.dzi'],
springStiffness: 100, // Faster animation = faster tests
showNavigator: true,
collectionMode: true
});
var openHandler = function() {
viewer.removeHandler('open', openHandler);
viewer.navigator.world.addHandler('add-item', navOpenHandler);
};
var navOpenHandler = function(event) {
navAddCount++;
if (navAddCount === 2) {
viewer.navigator.world.removeHandler('add-item', navOpenHandler);
setTimeout(function() {
// Test initial formation
equal(viewer.navigator.world.getItemCount(), 2, 'navigator has both items');
for (var i = 0; i < 2; i++) {
propEqual(viewer.navigator.world.getItemAt(i).getBounds(),
viewer.world.getItemAt(i).getBounds(), 'bounds are the same');
}
// Try moving one
viewer.world.getItemAt(0).setPosition(new OpenSeadragon.Point(-200, -200));
propEqual(viewer.navigator.world.getItemAt(0).getBounds(),
viewer.world.getItemAt(0).getBounds(), 'bounds are the same after move');
start();
}, 1);
}
};
viewer.addHandler('open', openHandler);
});
})();

View File

@ -3,7 +3,7 @@
(function () {
var viewer;
var VIEWER_ID = "example";
var PREFIX_URL = "/build/openseadragon/images";
var PREFIX_URL = "/build/openseadragon/images/";
var SPRING_STIFFNESS = 100; // Faster animation = faster tests
module("viewport", {
@ -207,7 +207,7 @@
method: 'getHomeBounds',
processExpected: function(level, expected) {
// Have to special case this to avoid dividing by 0
if(level === 0){
if(level === -1 || level === 0){
expected = new OpenSeadragon.Rect(0, 0, 1, 1);
} else {
var sideLength = 1.0 / viewer.defaultZoomLevel; // it's a square in this case
@ -243,8 +243,10 @@
viewport.zoomTo(ZOOM_FACTOR, null, true);
viewport.update(); // need to call this even with immediately=true
// If the default zoom level is set to 0, then we expect the home zoom to be 1.
if(level === 0){
// Special cases for oddball levels
if (level === -1) {
expected = 0.25;
} else if(level === 0){
expected = 1;
}
@ -423,9 +425,7 @@
return el.times(window_boundary);
},
getExpected: function(orig, viewport) {
var position, pos_point;
position = viewer.element.getBoundingClientRect();
pos_point = new OpenSeadragon.Point(position.top, position.left);
var pos_point = OpenSeadragon.getElementOffset(viewer.element);
return orig.minus(pos_point).divide(viewport.getContainerSize().x * ZOOM_FACTOR).plus(VIEWER_PADDING);
},
method: 'windowToViewportCoordinates'
@ -439,9 +439,7 @@
return el.times(viewer.source.dimensions.x);
},
getExpected: function(orig, viewport) {
var position, pos_point;
position = viewer.element.getBoundingClientRect();
pos_point = new OpenSeadragon.Point(position.top, position.left);
var pos_point = OpenSeadragon.getElementOffset(viewer.element);
return orig.plus(pos_point).minus(VIEWER_PADDING.times(viewport.getContainerSize().x * ZOOM_FACTOR));
},
method: 'imageToWindowCoordinates'
@ -456,9 +454,7 @@
return el.times(window_boundary);
},
getExpected: function(orig, viewport) {
var position, pos_point;
position = viewer.element.getBoundingClientRect();
pos_point = new OpenSeadragon.Point(position.top, position.left);
var pos_point = OpenSeadragon.getElementOffset(viewer.element);
return orig.minus(pos_point).divide(viewport.getContainerSize().x * ZOOM_FACTOR).plus(VIEWER_PADDING);
},
method: 'windowToViewportCoordinates'
@ -472,9 +468,7 @@
return el.times(viewer.source.dimensions.x);
},
getExpected: function(orig, viewport) {
var position, pos_point;
position = viewer.element.getBoundingClientRect();
pos_point = new OpenSeadragon.Point(position.top, position.left);
var pos_point = OpenSeadragon.getElementOffset(viewer.element);
return orig.minus(VIEWER_PADDING).times(viewport.getContainerSize().x * ZOOM_FACTOR).plus(pos_point);
},
method: 'viewportToWindowCoordinates'

View File

@ -30,7 +30,7 @@
<script src="/test/modules/multi-image.js"></script>
<script src="/test/modules/overlays.js"></script>
<script src="/test/modules/controls.js"></script>
<script src="/test/viewport.js"></script>
<script src="/test/modules/viewport.js"></script>
<script src="/test/modules/world.js"></script>
<script src="/test/modules/drawer.js"></script>
<script src="/test/modules/tiledimage.js"></script>