changeset 816:e6d0cdaa7923 stream

Merge from jquery branch 60e8cca7ac8125ec431f9b05a829c74aa2ca5ef6
author robcast
date Mon, 21 Feb 2011 10:15:09 +0100
parents 5de6c7a73855 (current diff) 60e8cca7ac81 (diff)
children 0b4345866797
files servlet/src/digilib/servlet/Scaler.java servlet/src/digilib/servlet/ServletOps.java
diffstat 8 files changed, 370 insertions(+), 161 deletions(-) [+]
line wrap: on
line diff
--- a/client/digitallibrary/jquery/jquery-digilib-plugins.txt	Sun Feb 20 19:34:05 2011 +0100
+++ b/client/digitallibrary/jquery/jquery-digilib-plugins.txt	Mon Feb 21 10:15:09 2011 +0100
@@ -40,3 +40,8 @@
   "update", updateDisplay(): after small updates in the display, e.g. when the scaler-img finished loading.
   "redisplay", redisplay(): after changes in the display, e.g. after changing zoom factor.
   "dragZoom(newZoomArea)": while dragging the zoom area (with parameter newZoomArea).
+
+* initial options for the plugin can be passed to digilib, together with digilib options, by passing an object to the 'digilib' function: $div.digilib(options);
+The passed options extend/override the default digilib options. The options are stored in the "data.settings" array.
+
+* A functional stub for new digilib plugins is available in the file "jquery.digilib.pluginstub.js"
--- a/client/digitallibrary/jquery/jquery-test-full.html	Sun Feb 20 19:34:05 2011 +0100
+++ b/client/digitallibrary/jquery/jquery-test-full.html	Mon Feb 21 10:15:09 2011 +0100
@@ -61,6 +61,7 @@
         <script type="text/javascript" src="jquery.digilib.geometry.js"></script>
         <script type="text/javascript" src="jquery.digilib.birdseye.js"></script>
         <script type="text/javascript" src="jquery.digilib.regions.js"></script>
+        <script type="text/javascript" src="jquery.digilib.pluginstub.js"></script>
         <link rel="stylesheet" type="text/css" href="jquery.digilib.css" />
 
 
@@ -68,7 +69,8 @@
             $(document).ready(function(){
                 var opts = {
                     interactionMode : 'fullscreen',
-                    scalerBaseUrl : 'http://digilib.biblhertz.it/digilib04/servlet/Scaler'
+                    scalerBaseUrl : 'http://digilib.biblhertz.it/digilib04/servlet/Scaler',
+                    showRegionNumbers : true
                     };
                 var $div = $('div.digilib');
                 $div.digilib(opts);
--- a/client/digitallibrary/jquery/jquery.digilib.birdseye.js	Sun Feb 20 19:34:05 2011 +0100
+++ b/client/digitallibrary/jquery/jquery.digilib.birdseye.js	Mon Feb 21 10:15:09 2011 +0100
@@ -7,6 +7,9 @@
     // affine geometry plugin stub
     var geom;
 
+    // digilib object
+    var digilib;
+
     var FULL_AREA;
 
     var buttons = {
@@ -44,7 +47,9 @@
     };
 
     // plugin installation called by digilib on plugin object.
-    var install = function(digilib) {
+    var install = function(plugin) {
+        digilib = plugin;
+        console.debug('installing birdseye plugin. digilib:', digilib);
         // import geometry classes
         geom = digilib.fn.geometry;
         FULL_AREA = geom.rectangle(0,0,1,1);
@@ -61,6 +66,7 @@
 
     // plugin initialization
     var init = function (data) {
+        console.debug('initialising birdseye plugin. data:', data);
         var $data = $(data);
         // install event handler
         $data.bind('setup', handleSetup);
@@ -227,6 +233,7 @@
             birdZoomRect = geom.rectangle($birdZoom);
             scalerPos = geom.position($scaler);
             newRect = null;
+            data.$elem.find(".overlay").hide(); // hide all overlays (marks/regions)
             fullRect = digilib.fn.setZoomBG(data); // setup zoom background image
             $document.bind("mousemove.dlBirdMove", birdZoomMove);
             $document.bind("mouseup.dlBirdMove", birdZoomEndDrag);
@@ -305,7 +312,7 @@
 
     // plugin object with name and init
     // shared objects filled by digilib on registration
-    var digilib = {
+    var plugin = {
             name : 'birdseye',
             install : install,
             init : init,
@@ -316,8 +323,8 @@
     };
 
     if ($.fn.digilib == null) {
-        $.error("jquery.digilib.birdview must be loaded after jquery.digilib!");
+        $.error("jquery.digilib.birdseye must be loaded after jquery.digilib!");
     } else {
-        $.fn.digilib('plugin', digilib);
+        $.fn.digilib('plugin', plugin);
     }
 })(jQuery);
--- a/client/digitallibrary/jquery/jquery.digilib.css	Sun Feb 20 19:34:05 2011 +0100
+++ b/client/digitallibrary/jquery/jquery.digilib.css	Mon Feb 21 10:15:09 2011 +0100
@@ -57,10 +57,24 @@
 }
 
 div.digilib div.region {
+	position: absolute;
 	background-color: red;
+	opacity: 0.3;
+}
+
+div.digilib div.region:hover {
 	opacity: 0.5;
 }
 
+div.regionnumber {
+    color: white;
+    font-size: 11px;
+	font-weight: bold;
+	height: 15px;
+	width: 16px;
+	margin: 3px;
+}
+
 /* special definitions for fullscreen */
 div.digilib.dl_fullscreen div.buttons {
 	position: fixed;
--- a/client/digitallibrary/jquery/jquery.digilib.geometry.js	Sun Feb 20 19:34:05 2011 +0100
+++ b/client/digitallibrary/jquery/jquery.digilib.geometry.js	Mon Feb 21 10:15:09 2011 +0100
@@ -178,8 +178,8 @@
             this.y = pos.y;
             return this;
         };
+        // adds pos to the position
         that.setPt1 = that.setPosition; // TODO: not really the same
-        // adds pos to the position
         that.addPosition = function(pos) {
             this.x += pos.x;
             this.y += pos.y;
@@ -198,14 +198,14 @@
                 y : this.y + this.height / 2
             });
         };
-        // moves this Rectangle's center to position pos
+        // moves this rectangle's center to position pos
         that.setCenter = function(pos) {
             this.x = pos.x - this.width / 2;
             this.y = pos.y - this.height / 2;
             return this;
         };
+        // returns true if both rectangles have equal position and proportion
         that.equals = function(other) {
-            // equal props
             var eq = (this.x === other.x && this.y === other.y && this.width === other.width);
             return eq;
         };
@@ -229,21 +229,20 @@
                     + this.height));
             return ct;
         };
-        // returns if rectangle "rect" is contained in this rectangle
+        // returns true if rectangle "rect" is contained in this rectangle
         that.containsRect = function(rect) {
             return (this.containsPosition(rect.getPt1()) && this
                     .containsPosition(rect.getPt2()));
         };
+        // returns true if rectangle "rect" and this rectangle overlap
+        that.overlapsRect = function(rect) {
+            return this.intersect(rect) != null;
+        };
         // changes this rectangle's x/y values so it stays inside of rectangle
-        // rect
-        // keeping the proportions
+        // "rect", keeping the proportions
         that.stayInside = function(rect) {
-            if (this.x < rect.x) {
-                this.x = rect.x;
-            }
-            if (this.y < rect.y) {
-                this.y = rect.y;
-            }
+            this.x = Math.max(this.x, rect.x);
+            this.y = Math.max(this.y, rect.y);
             if (this.x + this.width > rect.x + rect.width) {
                 this.x = rect.x + rect.width - this.width;
             }
@@ -252,7 +251,7 @@
             }
             return this;
         };
-        // clips this rectangle so it stays inside of rectangle rect
+        // clips this rectangle so it stays inside of rectangle "rect"
         that.clipTo = function(rect) {
             var p1 = rect.getPt1();
             var p2 = rect.getPt2();
@@ -261,27 +260,15 @@
             this.setPt2(position(Math.min(this2.x, p2.x), Math.min(this2.y, p2.y)));
             return this;
         };
-        // returns the intersection of the given Rectangle and this one
+        // returns the intersection of rectangle "rect" and this one
         that.intersect = function(rect) {
-            // FIX ME: not really, it should return null if there is no overlap
-            var sec = rect.copy();
-            if (sec.x < this.x) {
-                sec.width = sec.width - (this.x - sec.x);
-                sec.x = this.x;
-            }
-            if (sec.y < this.y) {
-                sec.height = sec.height - (this.y - sec.y);
-                sec.y = this.y;
-            }
-            if (sec.x + sec.width > this.x + this.width) {
-                sec.width = (this.x + this.width) - sec.x;
-            }
-            if (sec.y + sec.height > this.y + this.height) {
-                sec.height = (this.y + this.height) - sec.y;
-            }
-            return sec;
+            var res = rect.clipTo(this);
+            if (res.width < 0 || res.height < 0) res = null;
+            return res;
         };
-        // returns a Rectangle that fits into this one (by moving first)
+
+        // returns a copy of rectangle "rect" that fits into this one
+        // (moving it first)
         that.fit = function(rect) {
             var sec = rect.copy();
             sec.x = Math.max(sec.x, this.x);
@@ -294,7 +281,7 @@
             }
             return sec.intersect(this);
         };
-        // adjusts position and size of $elem to this rectangle
+        // adjusts position and size of jQuery element "$elem" to this rectangle
         that.adjustDiv = function($elem) {
             $elem.offset({
                 left : this.x,
@@ -302,7 +289,7 @@
             });
             $elem.width(this.width).height(this.height);
         };
-        // returns size and position in css-compatible format
+        // returns position and size of this rectangle in css-compatible format
         that.getAsCss = function() {
             return {
                 left : this.x,
@@ -311,6 +298,11 @@
                 height : this.height
             };
         };
+        // returns position and size of this rectangle formatted for SVG attributes
+        that.getAsSvg = function() {
+            return [this.x, this.y, this.width, this.height].join(" ");
+        };
+        // returns size and position of this rectangle formatted for ??? (w x h@x,y)
         that.toString = function() {
             return this.width + "x" + this.height + "@" + this.x + "," + this.y;
         };
--- a/client/digitallibrary/jquery/jquery.digilib.js	Sun Feb 20 19:34:05 2011 +0100
+++ b/client/digitallibrary/jquery/jquery.digilib.js	Mon Feb 21 10:15:09 2011 +0100
@@ -791,8 +791,9 @@
                 if (i) {
                     settings.mk += ',';
                     }
-                settings.mk += cropFloat(data.marks[i].x).toString() +
-                    '/' + cropFloat(data.marks[i].y).toString();
+                settings.mk +=
+                    cropFloatStr(data.marks[i].x) + '/' + 
+                    cropFloatStr(data.marks[i].y);
                 }
             }
         // Scaler flags
@@ -1236,7 +1237,7 @@
         var data = this;
         updateDisplay(data);
     };
-    
+
     // place marks on the image
     var renderMarks = function (data) {
         if (data.$img == null || data.imgTrafo == null) return;
@@ -1251,8 +1252,9 @@
                 var mpos = data.imgTrafo.transform(mark);
                 console.debug("renderMarks: mpos=",mpos);
                 // create mark
-                var html = '<div class="mark">'+(i+1)+'</div>';
+                var html = '<div class="mark overlay">'+(i+1)+'</div>';
                 var $mark = $(html);
+                $mark.attr("id", "digilib-mark-" + i);
                 $elem.append($mark);
                 mpos.adjustDiv($mark);
                 }
@@ -1362,8 +1364,6 @@
         var $scaler = data.$scaler;
         var $img = data.$img;
         var fullRect = null;
-        // hide marks
-        data.$elem.find('div.mark').hide();
         // hide the scaler img, show background of div instead
         $img.css('visibility', 'hidden');
         var scalerCss = {
@@ -1405,9 +1405,10 @@
 
         // drag the image and load a new detail on mouse up
         var dragStart = function (evt) {
-            console.debug("dragstart at=",evt);
+            console.debug("dragstart at=", evt);
             // don't start dragging if not zoomed
             if (isFullArea(data.zoomArea)) return false;
+            $elem.find(".overlay").hide(); // hide all overlays (marks/regions)
             startPos = geom.position(evt);
             delta = null;
             // set low res background immediately on mousedown
@@ -1430,7 +1431,7 @@
             $scaler.css({
                 'background-position' : bgPos.x + "px " + bgPos.y + "px"
                 });
-            // set birdview indicator to reflect new zoom position
+            // send message event with current zoom position
             var za = geom.rectangle($img);
             za.addPosition(delta.neg());
             $data.trigger('dragZoom', [za]);
@@ -1449,6 +1450,7 @@
                 $scaler.css({'opacity' : '1', 'background-image' : 'none'});
                 // unhide marks
                 data.$elem.find('div.mark').show();
+                $(data).trigger('redisplay');
                 return false; 
             }
             // get old zoom area (screen coordinates)
@@ -1553,6 +1555,11 @@
         return parseInt(10000 * x, 10) / 10000;
     };
 
+    // idem, string version
+    var cropFloatStr = function (x) {
+        return cropFloat(x).toString();
+    };
+
     // fallback for console.log calls
     if (customConsole) {
         var logFunction = function(type) {
@@ -1593,7 +1600,10 @@
             getScaleMode : getScaleMode,
             setScaleMode : setScaleMode,
             isFullArea : isFullArea,
-            getBorderWidth : getBorderWidth
+            isNumber : isNumber,
+            getBorderWidth : getBorderWidth,
+            cropFloat : cropFloat,
+            cropFloatStr : cropFloatStr
     };
 
     // hook digilib plugin into jquery
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/digitallibrary/jquery/jquery.digilib.pluginstub.js	Mon Feb 21 10:15:09 2011 +0100
@@ -0,0 +1,97 @@
+/**
+digilib plugin stub
+ */
+
+(function($) {
+
+    // affine geometry
+    var geom;
+    // plugin object with digilib data
+    var digilib;
+
+    var FULL_AREA;
+
+    var buttons = {
+            stub : {
+                onclick : ["doStub", 1],
+                tooltip : "what does this button do?",
+                icon : "stub.png"
+                }
+    };
+
+    var defaults = {
+            // is stub active?
+            'isStubActive' : true
+    };
+
+    var actions = {
+            // action code goes here 
+            doStub : function (data, param) {
+                var settings = data.settings;
+                console.log('isStubActive', settings.isStubActive);
+                // do some useful stuff ...
+            }
+    };
+
+    // plugin installation called by digilib on plugin object.
+    var install = function(plugin) {
+        digilib = plugin;
+        console.debug('installing stub plugin. digilib:', digilib);
+        // import geometry classes
+        geom = digilib.fn.geometry;
+        FULL_AREA = geom.rectangle(0,0,1,1);
+        // add defaults, actins, buttons
+        $.extend(digilib.defaults, defaults);
+        $.extend(digilib.actions, actions);
+        $.extend(digilib.buttons, buttons);
+    };
+
+    // plugin initialization
+    var init = function (data) {
+        console.debug('initialising stub plugin. data:', data);
+        var $data = $(data);
+        // install event handler
+        $data.bind('setup', handleSetup);
+        $data.bind('update', handleUpdate);
+        $data.bind('redisplay', handleRedisplay);
+        $data.bind('dragZoom', handleDragZoom);
+    };
+
+
+    var handleSetup = function (evt) {
+        console.debug("stub: handleSetup");
+        var data = this;
+    };
+
+    var handleUpdate = function (evt) {
+        console.debug("stub: handleUpdate");
+        var data = this;
+    };
+
+    var handleRedisplay = function (evt) {
+        console.debug("stub: handleRedisplay");
+        var data = this;
+    };
+
+    var handleDragZoom = function (evt, zoomArea) {
+        var data = this;
+    };
+
+    // plugin object with name and init
+    // shared objects filled by digilib on registration
+    var plugin = {
+            name : 'pluginstub',
+            install : install,
+            init : init,
+            buttons : {},
+            actions : {},
+            fn : {},
+            plugins : {}
+    };
+
+    if ($.fn.digilib == null) {
+        $.error("jquery.digilib.pluginstub must be loaded after jquery.digilib!");
+    } else {
+        $.fn.digilib('plugin', plugin);
+    }
+})(jQuery);
--- a/client/digitallibrary/jquery/jquery.digilib.regions.js	Sun Feb 20 19:34:05 2011 +0100
+++ b/client/digitallibrary/jquery/jquery.digilib.regions.js	Mon Feb 21 10:15:09 2011 +0100
@@ -11,19 +11,23 @@
 */
 
 (function($) {
+    // the digilib object
+    var digilib;
     // the data object passed by digilib
     var data;
-    var buttons;
+    // the functions made available by digilib
     var fn;
-    // affine geometry plugin stub
+    // affine geometry plugin
     var geom;
 
     var FULL_AREA;
 
+    var ID_PREFIX = "digilib-region-";
+
     var buttons = {
         addregion : {
-            onclick : "setRegion",
-            tooltip : "set a region",
+            onclick : "defineRegion",
+            tooltip : "define a region",
             icon : "addregion.png"
             },
         delregion : {
@@ -46,186 +50,260 @@
     var defaults = {
         // are regions shown?
         'isRegionVisible' : true,
+        // are region numbers shown?
+        'showRegionNumbers' : false,
         // buttonset of this plugin
         'regionSet' : ['addregion', 'delregion', 'regions', 'regioninfo', 'lessoptions'],
-        // array of defined regions
-        'regions' : []
-    };
+        // url param for regions
+        'rg' : null,
+        };
 
     var actions = { 
+
         // define a region interactively with two clicked points
-        "setRegion" : function(data) {
+        "defineRegion" : function(data) {
+            if (!data.settings.isRegionVisible) {
+                alert("Please turn on regions visibility!");
+                return;
+            }
             var $elem = data.$elem;
+            var $body = $('body');
+            var bodyRect = geom.rectangle($body);
             var $scaler = data.$scaler;
-            var picRect = geom.rectangle($scaler);
+            var scalerRect = geom.rectangle($scaler);
             var pt1, pt2;
-            // TODO: temporary rectangle only, pass values to "addRegion" factory
-            var $tempDiv = $('<div class="region" style="display:none"/>');
-            $elem.append($tempDiv);
+            // overlay prevents other elements from reacting to mouse events 
+            var $overlay = $('<div class="digilib-overlay"/>');
+            $body.append($overlay);
+            bodyRect.adjustDiv($overlay);
+             // we count regions from 1
+            var $regionDiv = addRegionDiv(data, data.regions.length + 1);
 
+            // mousedown handler: start sizing
             var regionStart = function (evt) {
                 pt1 = geom.position(evt);
                 // setup and show zoom div
-                pt1.adjustDiv($tempDiv);
-                $tempDiv.width(0).height(0);
-                $tempDiv.show();
-                // register events
-                $elem.bind("mousemove.dlRegion", regionMove);
-                $elem.bind("mouseup.dlRegion", regionEnd);
+                pt1.adjustDiv($regionDiv);
+                $regionDiv.width(0).height(0);
+                $regionDiv.show();
+                // register mouse events
+                $overlay.bind("mousemove.dlRegion", regionMove);
+                $overlay.bind("mouseup.dlRegion", regionEnd);
                 return false;
             };
 
-            // mouse move handler
+            // mousemove handler: size region
             var regionMove = function (evt) {
                 pt2 = geom.position(evt);
                 var rect = geom.rectangle(pt1, pt2);
-                rect.clipTo(picRect);
-                // update zoom div
-                rect.adjustDiv($tempDiv);
+                rect.clipTo(scalerRect);
+                // update region
+                rect.adjustDiv($regionDiv);
                 return false;
             };
 
-            // mouseup handler: end moving
+            // mouseup handler: end sizing
             var regionEnd = function (evt) {
                 pt2 = geom.position(evt);
                 // assume a click and continue if the area is too small
                 var clickRect = geom.rectangle(pt1, pt2);
                 if (clickRect.getArea() <= 5) return false;
-                // unregister events
-                $elem.unbind("mousemove.dlRegion", regionMove);
-                $elem.unbind("mouseup.dlRegion", regionEnd);
-                // clip and transform
-                clickRect.clipTo(picRect);
-                clickRect.adjustDiv($tempDiv);
-                $tempDiv.remove();
-                data.settings.regions.push(clickRect);
+                // unregister mouse events and get rid of overlay
+                $overlay.unbind("mousemove.dlRegion", regionMove);
+                $overlay.unbind("mouseup.dlRegion", regionEnd);
+                $overlay.remove();
+                // clip region
+                clickRect.clipTo(scalerRect);
+                clickRect.adjustDiv($regionDiv);
+                storeRegion(data, $regionDiv);
                 // fn.redisplay(data);
+                fn.highlightButtons(data, 'addregion', 0);
+                redisplay(data);
                 return false;
             };
 
-            // clear old handler (also ZoomDrag)
-            $scaler.unbind('.dlRegion');
-            $elem.unbind('.dlRegion');
             // bind start zoom handler
-            $scaler.one('mousedown.dlRegion', regionStart);
+            $overlay.one('mousedown.dlRegion', regionStart);
+            fn.highlightButtons(data, 'addregion', 1);
         },
 
         // remove the last added region
         "removeRegion" : function (data) {
-            var $regionDiv = data.settings.regions.pop();
+            if (!data.settings.isRegionVisible) {
+                alert("Please turn on regions visibility!");
+                return;
+            }
+            var region = data.regions.pop();
+            if (region == null) return;
+            var $regionDiv = region.$div; 
             $regionDiv.remove();
-            // fn.redisplay(data);
+            redisplay(data);
         },
 
-        // add a region programmatically
-        "addRegion" : function(data, pos, url) {
-            // TODO: backlink mechanism
-            if (pos.length === 4) {
-                // TODO: trafo
-                var $regionDiv = $('<div class="region" style="display:none"/>');
-                $regionDiv.attr("id", "region" + i);
-                var regionRect = geom.rectangle(pos[0], pos[1], pos[2], pos[3]);
-                regionRect.adjustDiv($regionDiv);
-                if (!data.regions) {
-                    data.regions = [];
-                    }
-                data.regions.push($regionDiv);
-            }
+        // show/hide regions 
+        "toggleRegions" : function (data) {
+            var show = !data.settings.isRegionVisible;
+            data.settings.isRegionVisible = show;
+            fn.highlightButtons(data, 'regions' , show);
+            showRegionDivs(data);
         }
     };
 
     var addRegion = actions.addRegion;
 
-    var realizeRegions = function (data) { 
-        // create regions from parameters
-        var settings = data.settings;
-        var rg = settings.rg;
-        var regions = rg.split(",");
-        for (var i = 0; i < regions.length ; i++) {
-            var pos = regions.split("/", 4);
-            // TODO: backlink mechanism
-            var url = paramString.match(/http.*$/);
-            addRegion(data, pos, url);
-            }
+    // store a region div
+    var storeRegion = function (data, $regionDiv) {
+        var regions = data.regions;
+        var rect = geom.rectangle($regionDiv);
+        var regionRect = data.imgTrafo.invtransform(rect);
+        regionRect.$div = $regionDiv;
+        regions.push(regionRect);
+        console.debug("regions", data.regions, "regionRect", regionRect);
+    };
+
+    // add a region to data.$elem
+    var addRegionDiv = function (data, nr) {
+        var $regionDiv = $('<div class="region overlay" style="display:none"/>');
+        $regionDiv.attr("id", ID_PREFIX + nr);
+        data.$elem.append($regionDiv);
+        if (data.settings.showRegionNumbers) {
+            var $regionNr = $('<div class="regionnumber" />');
+            $regionNr.text(nr);
+            $regionDiv.append($regionNr);
+        }
+        return $regionDiv;
+    };
+
+    // create a region div from the data.regions collection
+    var createRegionDiv = function (data, index) {
+        var regions = data.regions;
+        if (index > regions.length) return null;
+        var region = regions[index];
+        var $regionDiv = addRegionDiv(data, index + 1); // we count regions from 1
+        region.$div = $regionDiv;
+        // TODO store original coords in $regionDiv.data for embedded mode?
+        return $regionDiv;
+    };
+
+    // create regions 
+    var createRegionDivs = function (data) {
+        for (var i = 0; i < data.regions.length ; i++) {
+            createRegionDiv(data, i);
+        }
     };
 
-    // display current regions
-    var renderRegions = function (data) { 
+    // show a region on top of the scaler image 
+    var showRegionDiv = function (data, index) {
+        if (!data.imgTrafo) return;
+        var $elem = data.$elem;
         var regions = data.regions;
-        for (var i = 0; i < regions.length; i++) {
-            var region = regions[i];
-            if (data.zoomArea.containsPosition(region)) {
-                var rpos = data.imgTrafo.transform(region);
-                console.debug("renderRegions: rpos=", rpos);
-                // create region
-                var $regionDiv = $('<div class="region" style="display:none"/>');
-                $regionDiv.attr("id", "region" + data.regions.length);
-                $elem.append($regionDiv);
-                rpos.adjustDiv($regionDiv);
-                }
+        if (index > regions.length) return;
+        var region = regions[index]
+        var $regionDiv = region.$div;
+        if (!$regionDiv) {
+            console.debug("showRegionDiv: region has no $div", region);
+            // alert("showRegionDiv: region has no $div to show");
+            return;
+        }
+        var regionRect = region.copy();
+        var show = data.settings.isRegionVisible;
+        if (show && data.zoomArea.overlapsRect(regionRect)) {
+            regionRect.clipTo(data.zoomArea);
+            var screenRect = data.imgTrafo.transform(regionRect);
+            screenRect.adjustDiv($regionDiv);
+            $regionDiv.show();
+        } else {
+            $regionDiv.hide();
+        }
+    };
+
+    // show regions 
+    var showRegionDivs = function (data) {
+        for (var i = 0; i < data.regions.length ; i++) {
+            showRegionDiv(data, i);
+        }
+    };
+
+    var unpackRegions = function (data) { 
+        // create regions from parameters
+        var rg = data.settings.rg;
+        if (rg == null) return;
+        var regions = data.regions;
+        var rs = rg.split(",");
+        for (var i = 0; i < rs.length; i++) {
+            var r = rs[i];
+            var pos = r.split("/", 4);
+            var rect = geom.rectangle(pos[0], pos[1], pos[2], pos[3]);
+            regions.push(rect);
+            // TODO: backlink mechanism
+            // var url = paramString.match(/http.*$/);
             }
     };
 
-    var serializeRegions = function (data) {
-        if (data.regions) {
-            settings.rg = '';
-            for (var i = 0; i < data.regions.length; i++) {
-                if (i) {
-                    settings.rg += ',';
-                    }
-                settings.rg +=
-                    cropFloat(data.regions[i].x).toString() + '/' + 
-                    cropFloat(data.regions[i].y).toString() + '/' +
-                    cropFloat(data.regions[i].width).toString() + '/' +
-                    cropFloat(data.regions[i].height).toString();
-                }
+    // pack regions array into a parameter string
+    var packRegions = function (data) {
+        var regions = data.regions;
+        if (!regions.length) {
+            data.settings.rg = null;
+            return;
+        }
+        var rg = '';
+        for (var i = 0; i < regions.length; i++) {
+            region = regions[i];
+            if (i) {
+                rg += ',';
             }
+            rg += [
+                fn.cropFloatStr(region.x), 
+                fn.cropFloatStr(region.y),
+                fn.cropFloatStr(region.width),
+                fn.cropFloatStr(region.height)
+                ].join('/');
+        }
+        data.settings.rg = rg;
     };
 
+    var redisplay = function (data) {
+        packRegions(data);
+        fn.redisplay(data);
+    }
+
     var handleSetup = function (evt) {
-        console.debug("regions: handleSetup");
         data = this;
-//        if (data.settings.isBirdDivVisible) {
-//            setupBirdDiv(data);
-//            data.$birdDiv.show();
-//        }
+        console.debug("regions: handleSetup", data.settings.rg);
+        unpackRegions(data);
+        createRegionDivs(data);
     };
 
     var handleUpdate = function (evt) {
-        console.debug("regions: handleUpdate");
         data = this;
-//        if (data.settings.isBirdDivVisible) {
-//            renderBirdArea(data);
-//            setupBirdDrag(data);
-//        }
+        fn.highlightButtons(data, 'regions' , data.settings.isRegionVisible);
+        showRegionDivs(data);
+        console.debug("regions: handleUpdate", data.settings.rg);
     };
 
     var handleRedisplay = function (evt) {
+        data = this;
+        showRegionDivs(data);
         console.debug("regions: handleRedisplay");
-        data = this;
-//        if (data.settings.isBirdDivVisible) {
-//            updateBirdDiv(data);
-//        }
     };
 
     var handleDragZoom = function (evt, zoomArea) {
         console.debug("regions: handleDragZoom, zoomArea:", zoomArea);
         data = this;
-//        if (data.settings.isBirdDivVisible) {
-//            setBirdZoom(data, zoomArea);
-//        }
     };
 
     // plugin installation called by digilib on plugin object.
-    var install = function(digilib) {
+    var install = function(plugin) {
+        digilib = plugin;
+        console.debug('installing regions plugin. digilib:', digilib);
+        fn = digilib.fn;
         // import geometry classes
-        geom = digilib.fn.geometry;
+        geom = fn.geometry;
         FULL_AREA = geom.rectangle(0,0,1,1);
-        // add defaults
+        // add defaults, actions, buttons
         $.extend(digilib.defaults, defaults);
-        // add actions
         $.extend(digilib.actions, actions);
-        // add buttons
         $.extend(digilib.buttons, buttons);
     };
 
@@ -240,15 +318,19 @@
         if (buttonSet.length && buttonSet.length > 0) {
             buttonSettings['regionSet'] = buttonSet;
             buttonSettings.buttonSets.push('regionSet');
-            }
+        }
         // install event handler
         $data.bind('setup', handleSetup);
         $data.bind('update', handleUpdate);
         $data.bind('redisplay', handleRedisplay);
         $data.bind('dragZoom', handleDragZoom);
+        // regions array
+        data.regions = [];
+        // add "rg" to digilibParamNames
+        data.settings.digilibParamNames.push('rg');
     };
 
-    // plugin object with name and init
+    // plugin object with name and install/init methods
     // shared objects filled by digilib on registration
     var pluginProperties = {
             name : 'region',
@@ -261,7 +343,7 @@
     };
 
     if ($.fn.digilib == null) {
-        $.error("jquery.digilib.birdview must be loaded after jquery.digilib!");
+        $.error("jquery.digilib.regions must be loaded after jquery.digilib!");
     } else {
         $.fn.digilib('plugin', pluginProperties);
     }