diff --git a/changelog.txt b/changelog.txt index 8b7a991c..547646c4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,6 +4,7 @@ OPENSEADRAGON CHANGELOG 2.0.0: (in progress) * True multi-image mode (#450) + * BREAKING CHANGE: Passing an array for the tileSources option is no longer enough to trigger sequence mode; you have to set the sequenceMode option to true as well * BREAKING CHANGE: Navigator no longer sends an open event when its viewer opens * BREAKING CHANGE: Viewer.drawers and Viewer.drawersContainer no longer exist * BREAKING CHANGE: A Viewer's Drawer and Viewport are now made once per Viewer and reused for every image that Viewer opens (rather than being recreated for every open); this means if you change Viewer options between opens, the behavior is different now. @@ -35,6 +36,7 @@ OPENSEADRAGON CHANGELOG * New Viewport method for managing homeBounds as well as constraints: setHomeBounds * Viewport.open supports positioning config properties * 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 1.2.0: (in progress) diff --git a/src/openseadragon.js b/src/openseadragon.js index 70aeca84..5a40c560 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -446,8 +446,8 @@ * this setting when set to false. * * @property {Boolean} [showSequenceControl=true] - * If the viewer has been configured with a sequence of tile sources, then - * provide buttons for navigating forward and backward through the images. + * If sequenceMode is true, then provide buttons for navigating forward and + * backward through the images. * * @property {OpenSeadragon.ControlAnchor} [sequenceControlAnchor=TOP_LEFT] * Placement of the default sequence controls. @@ -505,18 +505,21 @@ * To only change the button images, consider using * {@link OpenSeadragon.Options.navImages} * + * @property {Boolean} [sequenceMode=false] + * Set to true to have the viewer treat your tilesources as a sequence of images to + * be opened one at a time rather than all at once. + * * @property {Number} [initialPage=0] - * If the viewer has been configured with a sequence of tile sources, display this page initially. + * If sequenceMode is true, display this page initially. * * @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' - * position. If preserveViewport is set to true, then the viewport position - * is preserved when navigating between images in the sequence. + * If sequenceMode is true, then normally navigating to through each image resets the + * viewport to 'home' position. If preserveViewport is set to true, then the viewport + * position is preserved when navigating between images in the sequence. * * @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. + * If sequenceMode is true, then display a scrolling strip of image thumbnails for + * navigating through the images. * * @property {String} [referenceStripScroll='horizontal'] * diff --git a/src/point.js b/src/point.js index 0004426b..1ceef296 100644 --- a/src/point.js +++ b/src/point.js @@ -196,7 +196,7 @@ $.Point.prototype = /** @lends OpenSeadragon.Point.prototype */{ * @returns {String} A string representation of this point. */ toString: function() { - return "(" + Math.round(this.x) + "," + Math.round(this.y) + ")"; + return "(" + (Math.round(this.x * 100) / 100) + "," + (Math.round(this.y * 100) / 100) + ")"; } }; diff --git a/src/rectangle.js b/src/rectangle.js index 217b5126..6c8c9243 100644 --- a/src/rectangle.js +++ b/src/rectangle.js @@ -264,10 +264,10 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ */ toString: function() { return "[" + - Math.round(this.x*100) + "," + - Math.round(this.y*100) + "," + - Math.round(this.width*100) + "x" + - Math.round(this.height*100) + + (Math.round(this.x*100) / 100) + "," + + (Math.round(this.y*100) / 100) + "," + + (Math.round(this.width*100) / 100) + "x" + + (Math.round(this.height*100) / 100) + "]"; } }; diff --git a/src/referencestrip.js b/src/referencestrip.js index f68744b2..0c0fdf61 100644 --- a/src/referencestrip.js +++ b/src/referencestrip.js @@ -289,6 +289,13 @@ $.extend( $.ReferenceStrip.prototype, $.EventSource.prototype, $.Viewer.prototyp return true; } return false; + }, + + // Overrides Viewer.destroy + destroy: function() { + if (this.element) { + this.element.parentNode.removeChild(this.element); + } } } ); @@ -467,7 +474,7 @@ function loadPanels( strip, viewerSize, scroll ) { */ function onStripEnter( event ) { var element = event.eventSource.element; - + //$.setElementOpacity(element, 0.8); //element.style.border = '1px solid #555'; @@ -495,7 +502,7 @@ function onStripEnter( event ) { */ function onStripExit( event ) { var element = event.eventSource.element; - + if ( 'horizontal' == this.scroll ) { //element.style.paddingTop = "10px"; diff --git a/src/viewer.js b/src/viewer.js index 2b2ea1f1..e5269a8b 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -200,13 +200,12 @@ $.Viewer = function( options ) { // how much we should be continuously zooming by "zoomFactor": null, "lastZoomTime": null, - // did we decide this viewer has a sequence of tile sources - "sequenced": false, - "sequence": 0, "fullPage": false, "onfullscreenchange": null }; + this._sequenceIndex = 0; + this._firstOpen = true; this._updateRequestId = null; this.currentOverlays = []; @@ -374,7 +373,6 @@ $.Viewer = function( options ) { } this.bindStandardControls(); - this.bindSequenceControls(); THIS[ this.hash ].prevContainerSize = _getSafeElemSize( this.container ); @@ -489,29 +487,14 @@ $.Viewer = function( options ) { }); } - //Instantiate a referencestrip if configured - if ( this.showReferenceStrip ){ - this.referenceStrip = new $.ReferenceStrip({ - id: this.referenceStripElement, - position: this.referenceStripPosition, - sizeRatio: this.referenceStripSizeRatio, - scroll: this.referenceStripScroll, - height: this.referenceStripHeight, - width: this.referenceStripWidth, - tileSources: this.tileSources, - tileHost: this.tileHost, - prefixUrl: this.prefixUrl, - viewer: this - }); + // Sequence mode + if (this.sequenceMode) { + this.bindSequenceControls(); } // Open initial tilesources - if ( this.tileSources ) { + if ( this.tileSources && this.tileSources.length) { this.open( this.tileSources ); - - if ( this.tileSources.length > 1 ) { - this._updateSequenceButtons( this.initialPage ); - } } // Add custom controls @@ -573,6 +556,37 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, return; } + if (this.sequenceMode && $.isArray(tileSources)) { + if (this.referenceStrip) { + this.referenceStrip.destroy(); + this.referenceStrip = null; + } + + this.tileSources = tileSources; + this._sequenceIndex = Math.max(0, Math.min(this.tileSources.length - 1, this.initialPage)); + if (this.tileSources.length) { + this.open(this.tileSources[this._sequenceIndex]); + + if ( this.showReferenceStrip ){ + this.referenceStrip = new $.ReferenceStrip({ + id: this.referenceStripElement, + position: this.referenceStripPosition, + sizeRatio: this.referenceStripSizeRatio, + scroll: this.referenceStripScroll, + height: this.referenceStripHeight, + width: this.referenceStripWidth, + tileSources: this.tileSources, + tileHost: this.tileHost, + prefixUrl: this.prefixUrl, + viewer: this + }); + } + } + + this._updateSequenceButtons( this._sequenceIndex ); + return; + } + if (!$.isArray(tileSources)) { tileSources = [tileSources]; } @@ -589,10 +603,12 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, var checkCompletion = function() { if (successes + failures === expected) { if (successes) { - if (!_this.preserveViewport) { + if (_this._firstOpen || !_this.preserveViewport) { _this.viewport.goHome( true ); } + _this._firstOpen = false; + var source = tileSources[0]; if (source.tileSource) { source = source.tileSource; @@ -751,6 +767,11 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, //this.unbindSequenceControls() //this.unbindStandardControls() + if (this.referenceStrip) { + this.referenceStrip.destroy(); + this.referenceStrip = null; + } + if ( this._updateRequestId !== null ) { $.cancelAnimationFrame( this._updateRequestId ); this._updateRequestId = null; @@ -1411,7 +1432,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, navImages = this.navImages, useGroup = true ; - if( this.showSequenceControl && THIS[ this.hash ].sequenced ){ + if( this.showSequenceControl ){ if( this.previousButton || this.nextButton ){ //if we are binding to custom buttons then layout and @@ -1451,6 +1472,10 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, this.previousButton.disable(); } + if (!this.tileSources || !this.tileSources.length) { + this.nextButton.disable(); + } + if( useGroup ){ this.paging = new $.ButtonGroup({ buttons: [ @@ -1648,7 +1673,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * @return {Number} */ currentPage: function() { - return THIS[ this.hash ].sequence; + return this._sequenceIndex; }, /** @@ -1657,7 +1682,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * @fires OpenSeadragon.Viewer.event:page */ goToPage: function( page ){ - if( page >= 0 && page < this.tileSources.length ){ + if( this.tileSources && page >= 0 && page < this.tileSources.length ){ /** * Raised when the page is changed on a viewer configured with multiple image sources (see {@link OpenSeadragon.Viewer#goToPage}). * @@ -1670,7 +1695,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, */ this.raiseEvent( 'page', { page: page } ); - THIS[ this.hash ].sequence = page; + this._sequenceIndex = page; this._updateSequenceButtons( page ); @@ -1860,7 +1885,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, _updateSequenceButtons: function( page ) { if ( this.nextButton ) { - if( ( this.tileSources.length - 1 ) === page ) { + if(!this.tileSources || this.tileSources.length - 1 === page) { //Disable next button if ( !this.navPrevNextWrap ) { this.nextButton.disable(); @@ -2845,7 +2870,7 @@ function onRotateRight() { function onPrevious(){ - var previous = THIS[ this.hash ].sequence - 1; + var previous = this._sequenceIndex - 1; if(this.navPrevNextWrap && previous < 0){ previous += this.tileSources.length; } @@ -2854,7 +2879,7 @@ function onPrevious(){ function onNext(){ - var next = THIS[ this.hash ].sequence + 1; + var next = this._sequenceIndex + 1; if(this.navPrevNextWrap && next >= this.tileSources.length){ next = 0; } diff --git a/src/world.js b/src/world.js index 9dc47b45..2c7a3f7f 100644 --- a/src/world.js +++ b/src/world.js @@ -324,6 +324,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W if ( !this._items.length ) { this._homeBounds = new $.Rect(0, 0, 1, 1); this._contentSize = new $.Point(1, 1); + this._contentFactor = 1; } else { var bounds = this._items[0].getBounds(); this._contentFactor = this._items[0].getContentSize().x / bounds.width; @@ -349,7 +350,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W if (this._contentFactor !== oldContentFactor || !this._homeBounds.equals(oldHomeBounds) || !this._contentSize.equals(oldContentSize)) { /** - * Raised when the home bounds, content size, or content factor change. + * Raised when the home bounds or content factor change. * @event metrics-change * @memberOf OpenSeadragon.World * @type {object} diff --git a/test/controls.js b/test/controls.js index f0f008fe..640b2402 100644 --- a/test/controls.js +++ b/test/controls.js @@ -278,10 +278,6 @@ asyncTest('SequenceControlOnPrevNextWrapOff', function () { - expect(0); - start(); - return; // Temporarily disabling - var openHandler = function () { viewer.removeHandler('open', openHandler); ok(viewer.showSequenceControl, 'showSequenceControl should be on'); @@ -333,6 +329,7 @@ ], springStiffness: 100, // Faster animation = faster tests showSequenceControl: true, + sequenceMode: true, navPrevNextWrap: false }); viewer.addHandler('open', openHandler); @@ -340,10 +337,6 @@ asyncTest('SequenceControlOnPrevNextWrapOn', function () { - expect(0); - start(); - return; // Temporarily disabling - var openHandler = function () { viewer.removeHandler('open', openHandler); ok(viewer.showSequenceControl, 'showSequenceControl should be on'); @@ -388,6 +381,7 @@ ], springStiffness: 100, // Faster animation = faster tests showSequenceControl: true, + sequenceMode: true, navPrevNextWrap: true }); viewer.addHandler('open', openHandler); diff --git a/test/demo/collections/main.js b/test/demo/collections/main.js index 426015b2..d1f9c652 100644 --- a/test/demo/collections/main.js +++ b/test/demo/collections/main.js @@ -16,9 +16,14 @@ // debugMode: true, zoomPerScroll: 1.02, showNavigator: testNavigator, - collectionMode: true, - collectionRows: 3, - collectionLayout: 'vertical', + sequenceMode: true, + showReferenceStrip: true, + // referenceStripScroll: 'vertical', + navPrevNextWrap: false, + preserveViewport: false, + // collectionMode: true, + // collectionRows: 3, + // collectionLayout: 'vertical', // collectionTileSize: 10, // collectionTileMargin: 10, // wrapHorizontal: true, @@ -34,7 +39,8 @@ x: 1.5, y: 0, width: 1 - }, { + }, + { tileSource: '../../data/wide.dzi', opacity: 1, x: 0, @@ -100,8 +106,9 @@ }); } - // this.crossTest3(); - this.collectionTest(); + if (!testInitialOpen) { + this.collectionTest(); + } }, // ---------- diff --git a/test/test.html b/test/test.html index 88a04135..54b5de05 100644 --- a/test/test.html +++ b/test/test.html @@ -31,6 +31,7 @@ + diff --git a/test/world.js b/test/world.js new file mode 100644 index 00000000..634165d8 --- /dev/null +++ b/test/world.js @@ -0,0 +1,226 @@ +/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */ + +(function() { + var viewer; + + module('World', { + setup: function () { + var example = $('
').appendTo("#qunit-fixture"); + + testLog.reset(); + + viewer = OpenSeadragon({ + id: 'example', + prefixUrl: '/build/openseadragon/images/', + springStiffness: 100 // Faster animation = faster tests + }); + }, + teardown: function () { + if (viewer && viewer.close) { + viewer.close(); + } + + viewer = null; + } + }); + + // ---------- + var checkBounds = function(expected, message) { + var bounds = viewer.world.getHomeBounds(); + ok(bounds.equals(expected), message + ' ' + bounds.toString()); + }; + + // ---------- + asyncTest('adding a tiled image', function() { + ok(viewer.world, 'World exists'); + + viewer.world.addHandler('add-item', function(event) { + ok(event, 'add-item handler received event data'); + equal(event.eventSource, viewer.world, 'sender of add-item event was world'); + ok(event.item, 'add-item event includes item'); + equal(viewer.world.getItemCount(), 1, 'there is now 1 item'); + equal(event.item, viewer.world.getItemAt(0), 'item is accessible via getItemAt'); + equal(viewer.world.getIndexOfItem(event.item), 0, 'item index is 0'); + start(); + }); + + equal(viewer.world.getItemCount(), 0, 'no items to start with'); + + viewer.open('/test/data/testpattern.dzi'); + }); + + // ---------- + asyncTest('metrics', function() { + viewer.addHandler('open', function(event) { + checkBounds(new OpenSeadragon.Rect(0, 0, 4, 4), 'bounds after open'); + + var expectedContentFactor = viewer.world.getItemAt(1).getContentSize().x / 2; + equal(viewer.world.getContentFactor(), expectedContentFactor, 'content factor has changed'); + + viewer.world.addHandler('metrics-change', function metricsChangeHandler(event) { + viewer.world.removeHandler('metrics-change', metricsChangeHandler); + ok(event, 'metrics-change handler received event data'); + equal(event.eventSource, viewer.world, 'sender of metrics-change event was world'); + checkBounds(new OpenSeadragon.Rect(0, 0, 7, 12), 'bounds after position'); + viewer.world.getItemAt(0).setWidth(20); + checkBounds(new OpenSeadragon.Rect(0, 0, 20, 20), 'bounds after size'); + + start(); + }); + + viewer.world.getItemAt(1).setPosition(new OpenSeadragon.Point(5, 10)); + }); + + checkBounds(new OpenSeadragon.Rect(0, 0, 1, 1), 'default bounds'); + equal(viewer.world.getContentFactor(), 1, 'default content factor'); + + viewer.open([ + { + tileSource: '/test/data/testpattern.dzi', + width: 4 + }, { + tileSource: '/test/data/testpattern.dzi', + width: 2 + } + ]); + }); + + // ---------- + asyncTest('remove/reorder tiled images', function() { + var handlerCount = 0; + + viewer.addHandler('open', function(event) { + equal(viewer.world.getItemCount(), 3, 'there are now 3 items'); + var item0 = viewer.world.getItemAt(0); + var item1 = viewer.world.getItemAt(1); + + viewer.world.addHandler('item-index-change', function(event) { + handlerCount++; + ok(event, 'item-index-change handler received event data'); + equal(event.eventSource, viewer.world, 'sender of item-index-change event was world'); + equal(event.item, item0, 'item-index-change event includes correct item'); + equal(event.newIndex, 1, 'item-index-change event includes correct newIndex'); + equal(event.previousIndex, 0, 'item-index-change event includes correct previousIndex'); + equal(viewer.world.getItemAt(0), item1, 'item1 is now at index 0'); + equal(viewer.world.getItemAt(1), item0, 'item0 is now at index 1'); + }); + + viewer.world.setItemIndex(item0, 1); + + viewer.world.addHandler('remove-item', function removeHandler(event) { + viewer.world.removeHandler('remove-item', removeHandler); + handlerCount++; + ok(event, 'remove-item handler received event data'); + equal(event.eventSource, viewer.world, 'sender of remove-item event was world'); + equal(event.item, item1, 'remove-item event includes correct item'); + equal(viewer.world.getItemCount(), 2, 'after removal, only two items remain'); + equal(viewer.world.getItemAt(0), item0, 'item0 is now at index 0'); + }); + + viewer.world.removeItem(item1); + + var removeCount = 0; + viewer.world.addHandler('remove-item', function() { + removeCount++; + if (removeCount === 2) { + handlerCount++; + equal(viewer.world.getItemCount(), 0, 'after removeAll, no items remain'); + } + }); + + viewer.world.removeAll(); + + equal(handlerCount, 3, 'correct number of handlers called'); + start(); + }); + + equal(viewer.world.getItemCount(), 0, 'no items to start with'); + + viewer.open([ + '/test/data/testpattern.dzi', + '/test/data/testpattern.dzi', + '/test/data/testpattern.dzi' + ]); + }); + + // ---------- + asyncTest('update', function() { + var handlerCount = 0; + + viewer.addHandler('open', function(event) { + equal(viewer.world.needsUpdate(), true, 'needs update after open'); + + viewer.addHandler('update-level', function updateHandler() { + viewer.removeHandler('update-level', updateHandler); + handlerCount++; + }); + + viewer.world.update(); + + equal(handlerCount, 1, 'correct number of handlers called'); + start(); + }); + + equal(viewer.world.needsUpdate(), false, 'needs no update at first'); + + viewer.open('/test/data/testpattern.dzi'); + }); + + // ---------- + asyncTest('resetItems', function() { + viewer.addHandler('tile-drawn', function updateHandler() { + viewer.removeHandler('tile-drawn', updateHandler); + ok(viewer.tileCache.numTilesLoaded() > 0, 'we have tiles after tile-drawn'); + viewer.world.resetItems(); + equal(viewer.tileCache.numTilesLoaded(), 0, 'no tiles after reset'); + start(); + }); + + equal(viewer.tileCache.numTilesLoaded(), 0, 'no tiles at start'); + + viewer.open('/test/data/testpattern.dzi'); + }); + + // ---------- + asyncTest('arrange', function() { + viewer.addHandler('open', function(event) { + checkBounds(new OpenSeadragon.Rect(0, 0, 1, 1), 'all stacked'); + + viewer.world.arrange({ + layout: 'horizontal', + rows: 1, + tileSize: 1, + tileMargin: 0.5 + }); + + checkBounds(new OpenSeadragon.Rect(0, 0, 4, 1), 'one horizontal row'); + + viewer.world.arrange({ + layout: 'horizontal', + rows: 2, + tileSize: 1, + tileMargin: 0.5 + }); + + checkBounds(new OpenSeadragon.Rect(0, 0, 2.5, 2.5), 'grid'); + + viewer.world.arrange({ + layout: 'vertical', + rows: 1, + tileSize: 1, + tileMargin: 0.5 + }); + + checkBounds(new OpenSeadragon.Rect(0, 0, 1, 4), 'one vertical column'); + + start(); + }); + + viewer.open([ + '/test/data/testpattern.dzi', + '/test/data/testpattern.dzi', + '/test/data/testpattern.dzi' + ]); + }); + +})();