OpenSeaDragonAnnotation.js 47.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/* 
OpenSeaDragonAnnotation v1.0 (http://)
Copyright (C) 2014 CHS (Harvard University), Daniel Cebrián Robles and Phil Desenne
License: https://github.com/CtrHellenicStudies/OpenSeaDragonAnnotation/blob/master/License.rst

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
(function($) {
21 22 23 24 25 26 27
    $.Viewer.prototype.annotation = function(options) {
        //-- wait for plugins --//
        var wrapper = jQuery('.annotator-wrapper').parent()[0],
            annotator = jQuery.data(wrapper, 'annotator'),
            self = this,
            isOpenViewer = false;
        
28 29 30 31
        /**
         * Sets up a call so that every time the OpenSeaDragon instance is opened
         * it triggers the annotations to be redrawn.
         */
32 33 34 35 36 37
        this.addHandler("open", function() {
            isOpenViewer = true;
            if (typeof self.annotationInstance!='undefined')
                self.annotationInstance.refreshDisplay();
        });
        annotator
38 39 40 41 42 43
            /**
             * This function is called once annotator has loaded the annotations.
             * It will then wait until the OSD instance has loaded to start drawing
             * the annotations.
             * @param Array annotations list of annotations from annotator instance
             */
44 45
            .subscribe("annotationsLoaded", function (annotations){
                if (!self.annotationInstance) {
46
                
47
                    // annotation instance should include the OSD item and annotator
48 49 50 51
                    self.annotationInstance = new $._annotation({
                        viewer: self,
                        annotator: annotator,
                    });
52
                    
53 54
                    // this collection of items is included as an item of annotator so
                    // that there is a method to communicate back and forth. 
55
                    annotator.osda = self.annotationInstance;
56
                    
57 58
                    // Because it takes a while for both OSD to open and for annotator
                    // to get items from the backend, we wait until we get the "open" call
59 60 61 62 63 64 65 66 67 68 69 70 71
                    function refreshDisplay(){
                        if(!isOpenViewer){
                            setTimeout(refreshDisplay,200);
                        }else{
                            self.annotationInstance.refreshDisplay();
                        }
                    }
                    refreshDisplay();
                } else {
                    self.annotationInstance.refreshDisplay();
                }
            });
    };
72 73 74 75 76
    
    /**
     * Instance of the annotation package including OSD and Annotator
     * @constructor
     */
77
    $._annotation = function(options) {
78
        // options
79 80
        options = options || {};
        if (!options.viewer) {
81 82
            throw new Error("A viewer must be specified.");
        }
83

84
        // variables
85 86 87
        this.viewer = options.viewer;
        this.annotator = options.annotator;
        this.options = options;
88 89
        this.isAnnotating = false; // If the user is annotating
        this.isDrawing = false; // if the user is drawing something
90 91
        this.rectPosition = undefined;
        
92
        // Init
93
        this.init();
94 95 96 97
    };
    
    //-- Methods
    $._annotation.prototype = {
98 99 100 101
        /**
         * This function makes sure that the OSD buttons are created, that the
         * panning and zooming functionality is created and the annotation events.
         */
102 103 104
        init: function(){
            var viewer = this.viewer;
            
105
            // create Buttons
106 107 108
            this._createNewButton();
            
            /* canvas Events */
109
            // Bind canvas functions
110 111 112 113
            var onCanvasMouseDown = this.__bind(this._onCanvasMouseDown,this);
            var onCanvasMouseMove = this.__bind(this._onCanvasMouseMove,this);
            var onDocumentMouseUp = this.__bind(this._onDocumentMouseUp,this);
                
114
            // Add canvas events
115 116 117 118
            $.addEvent(viewer.canvas, "mousedown", onCanvasMouseDown, true);
            $.addEvent(viewer.canvas, "mousemove", onCanvasMouseMove, true);
            $.addEvent(document, "mouseup", onDocumentMouseUp, true);
            
119
            // Viewer events
120 121
            var self = this;
        },
122 123 124 125 126 127
        
        /**
         * This function is called when the user changed from panning/zooming mode to
         * annotation creation mode. It allows the annotator to accept the creation of
         * a new annotation. 
         */
128 129 130
        newAnnotation:function(){
            var annotator = this.annotator;
            
131
            // This variable tells editor that we want create an image annotation
132
            annotator.editor.OpenSeaDragon = this.viewer.id;
133 134
            
            // allows the adder to actually show up
135 136
            annotator.adder.show();
            
137 138
            // takes into account the various wrappers and instances to put the shape
            // over the correct place. 
139 140
            this._setOverShape(annotator.adder);
            
141
            // Open a new annotator dialog
142 143
            annotator.onAdderClick();
        },
144 145 146 147 148 149
        
        /**
         * This function simply allows the editor to pop up with the given annotation.
         * @param {Object} annotation The annotation item from the backend server.
         * @param {TinyMCEEditor} editor The item that pops up when you edit an annotation.
         */
150
        editAnnotation: function(annotation,editor){
151
            // Stupid check: is the annotation you're trying to edit an image?
152
            if (this._isOpenSeaDragon(annotation)){
153
                
154 155
                var editor = editor || this.annotator.editor;
            
156
                // set the editor over the highlighted element
157 158 159
                this._setOverShape(editor.element);
                editor.checkOrientation();
            
160
                // makes sure that we are making an image annotation
161 162 163
                editor.OpenSeaDragon = this.viewer.id;
            }
        },
164 165 166 167 168 169
        
        /**
         * This function gets the annotations from the last annotator query and sorts
         * them and draws them onto the OpenSeaDragon instance. It also publishes
         * a notification in case the colorize the annotations. 
         */
170 171 172 173
        refreshDisplay: function(){
            var allannotations = this.annotator.plugins['Store'].annotations;
            var annotator = this.annotator;
        
174
            // Sort the annotations by date
175 176
            this._sortByDate(allannotations);
        
177
            // remove all of the overlays
178 179 180 181 182
            this.viewer.drawer.clearOverlays();
        
            for (var item in allannotations) {
                var an = allannotations[item];
            
183
                // check if the annotation is an OpenSeaDragon annotation
184 185
                if (this._isOpenSeaDragon(an)){
                    this.drawRect(an);    
186 187 188 189
                }
            }
            
            // if the colored highlights by tags plugin it is notified to colorize
190
            annotator.publish('externalCallToHighlightTags', [an]);
191
        },
192 193 194 195 196 197
        
        /**
         * This function get notified every time we switch from panning/zooming mode onto
         * annotation creation mode. 
         * @param {Event} e This is the event passed in from the OSD buttons.
         */
198 199 200 201
        modeAnnotation:function(e){
            this._reset();
            var viewer = this.viewer;
            if (!this.isAnnotating){
202 203
                // When annotating, the cursor turns into a crosshair and there is a
                // green border around the OSD instance.
204 205 206 207 208 209
                jQuery('.openseadragon1').css('cursor', 'crosshair');
                jQuery('.openseadragon1').css('border', '2px solid rgb(51,204,102)');
                e.eventSource.imgGroup.src =  this.resolveUrl( viewer.prefixUrl,"newan_hover.png");
                e.eventSource.imgRest.src =  this.resolveUrl( viewer.prefixUrl,"newan_hover.png");
                e.eventSource.imgHover.src = this.resolveUrl( viewer.prefixUrl,"newan_grouphover.png");
            }else{
210
                // Otherwise, the cursor is a cross with four arrows to indicate movement
211 212 213 214 215 216
                jQuery('.openseadragon1').css('cursor', 'all-scroll');
                jQuery('.openseadragon1').css('border', 'inherit');
                e.eventSource.imgGroup.src =  this.resolveUrl( viewer.prefixUrl,"newan_grouphover.png");
                e.eventSource.imgRest.src =  this.resolveUrl( viewer.prefixUrl,"newan_rest.png");
                e.eventSource.imgHover.src =  this.resolveUrl( viewer.prefixUrl,"newan_hover.png");
            }
217
            
218
            // toggles the annotating flag
219 220
            this.isAnnotating = !this.isAnnotating?true:false;
        },
221 222 223 224 225 226
        
        /**
         * This function takes in an annotation and draws the box indicating the area
         * that has been annotated. 
         * @param {Object} an Annotation item from the list in the Annotator instance.
         */
227
        drawRect:function(an){
228
            // Stupid check: Does this annotation actually have an area of annotation
229
            if (typeof an.rangePosition!='undefined'){
230
                // Sets up the visual aspects of the area for the user
231 232 233
                var span = document.createElement('span');
                var rectPosition = an.rangePosition;
                span.className = "annotator-hl";
234 235 236 237 238
                
                // outline and border below create a double line one black and one white
                // so to be able to differentiate when selecting dark or light images
                span.style.border = '2px solid rgb(255, 255, 255)';
                span.style.outline = '2px solid rgb(0, 0, 0)';
239
                span.style.background = 'rgba(0,0,0,0)';
240
                
241
                // Adds listening items for the viewer and editor
242 243 244 245 246
                var onAnnotationMouseMove = this.__bind(this._onAnnotationMouseMove,this);
                var onAnnotationClick = this.__bind(this._onAnnotationClick,this);
                $.addEvent(span, "mousemove", onAnnotationMouseMove, true);
                $.addEvent(span, "click", onAnnotationClick, true);
                
247
                // Set the object in the div
248
                jQuery.data(span, 'annotation', an);
249
                
250
                // Add the highlights to the annotation
251
                an.highlights = jQuery(span);
252 253
                
                // Sends the element created to the proper location within the OSD instance
254 255 256 257 258 259 260 261 262
                var olRect = new OpenSeadragon.Rect(rectPosition.left, rectPosition.top, rectPosition.width, rectPosition.height);
                return this.viewer.drawer.addOverlay({
                    element: span,
                    location: olRect,
                    placement: OpenSeadragon.OverlayPlacement.TOP_LEFT
                });
            }
            return false;
        },
263 264 265 266 267
        
        /**
         * This changes the variable rectPosition to the proper location based on
         * screen coordinates and OSD image coordinates. 
         */
268
        setRectPosition:function(){
269
            // Get the actual locations of the rectangle
270 271
            var left = parseInt(this.rect.style.left);
            var top = parseInt(this.rect.style.top);
272 273
            var width = parseInt(this.rect.style.left) + parseInt(this.rect.style.width);
            var height = parseInt(this.rect.style.top) + parseInt(this.rect.style.height);
274 275
            var startPoint = new $.Point(left,top);
            var endPoint = new $.Point(width,height);
276
            
277
            // return the proper value of the rectangle
278 279
            this.rectPosition = {left:this._physicalToLogicalXY(startPoint).x,
                top:this._physicalToLogicalXY(startPoint).y,
280 281
                width:this._physicalToLogicalXY(endPoint).x - this._physicalToLogicalXY(startPoint).x,
                height:this._physicalToLogicalXY(endPoint).y - this._physicalToLogicalXY(startPoint).y
282 283
            };
        },
284
        
285
        /* Handlers */
286 287 288 289 290 291 292 293
        
        /**
         * When the user starts clicking this will create a rectangle that will be a
         * temporary position that is to be later scaled via dragging
         * @param {Event} event The actual action of clicking down.
         */
        _onCanvasMouseDown: function(event) {
            
294
            // action is ONLY performed if we are in annotation creation mode
295 296 297 298
            if (this.isAnnotating){
                var viewer = this.viewer;
                event.preventDefault();
                
299
                // reset the display
300 301
                this._reset();
                
302
                // set mode drawing
303 304
                this.isDrawing = true;
                
305
                // Create rect element
306 307 308 309 310
                var mouse  = $.getMousePosition( event );
                var elementPosition = $.getElementPosition(viewer.canvas);
                var position = mouse.minus( elementPosition );
                viewer.innerTracker.setTracking(false);
                this.rect = document.createElement('div');
311
                this.rect.style.background = 'rgba(0,0,0,0)';
312 313 314 315 316 317
                
                // outline and border below create a double line one black and one white
                // so to be able to differentiate when selecting dark or light images
                this.rect.style.border = '2px solid rgb(255, 255, 255)';
                this.rect.style.outline = '2px solid rgb(0, 0, 0)';
                
318 319
                this.rect.style.position = 'absolute';
                this.rect.className = 'DrawingRect';
320 321 322
                // set the initial position
                this.rect.style.top = position.y + "px";
                this.rect.style.left = position.x + "px";
323 324 325
                this.rect.style.width = "1px";
                this.rect.style.height = "1px";
                
326
                // save the start Position
327
                this.startPosition = position;
328
                // save rectPosition as initial rectangle parameter to Draw in the canvas
329 330
                this.setRectPosition();
                
331
                // append Child to the canvas
332 333 334
                viewer.canvas.appendChild(this.rect);
            }
        },
335 336 337 338 339
        /**
         * When the user has clicked and is now dragging to create an annotation area,
         * the following function resizes the area selected. 
         * @param {Event} event The actual action of dragging every time it is dragged.
         */
340
        _onCanvasMouseMove: function(event) {
341
        
342 343
            // of course, this only runs when we are in annotation creation mode and
            // when the user has clicked down (and is therefore drawing the rectangle)
344 345 346
            if (this.isAnnotating && this.isDrawing){ 
                var viewer = this.viewer;
                
347
                // Calculate the new end position
348 349 350
                var mouse  = $.getMousePosition( event );
                var elementPosition = $.getElementPosition(viewer.canvas);
                var endPosition = mouse.minus( elementPosition );
351
                // retrieve start position    
352 353 354 355 356
                var startPosition = this.startPosition;
                
                var newWidth= endPosition.x-startPosition.x;
                var newHeight =endPosition.y-startPosition.y;
                
357 358 359 360 361
                // Set new position
                this.rect.style.width = (newWidth < 0) ? (-1*newWidth) + 'px' : newWidth + 'px';
                this.rect.style.left = (newWidth < 0) ? (startPosition.x + newWidth) + 'px' : startPosition.x + 'px';
                this.rect.style.height = (newHeight < 0) ? (-1*newHeight) + 'px' : newHeight + 'px';
                this.rect.style.top = (newHeight < 0) ? (startPosition.y + newHeight) + 'px' : startPosition.y + 'px';
362
                
363
                // Modify the rectPosition with the new this.rect values
364 365
                this.setRectPosition();
                
366
                // Show adder and hide editor
367 368 369 370
                this.annotator.editor.element[0].style.display = 'none';
                this._setOverShape(this.annotator.adder);
            }
        },
371 372 373 374 375
        
        /**
         * This function will finish drawing the rectangle, get its current position
         * and then open up the editor to make the annotation.
         */
376
        _onDocumentMouseUp: function() {
377
        
378 379
            // Stupid check: only do it when in annotation creation mode and
            // when the user has begun making a rectangle over the annotation area
380 381 382 383 384 385
            if (this.isAnnotating && this.isDrawing){
                var viewer = this.viewer;
                
                viewer.innerTracker.setTracking(true);
                this.isDrawing = false;
                
386
                // Set the new position for the rectangle
387 388
                this.setRectPosition();
                
389
                // Open Annotator editor
390 391
                this.newAnnotation();
                
392
                // Hide adder and show editor
393 394 395 396 397
                this.annotator.editor.element[0].style.display = 'block';
                this._setOverShape(this.annotator.editor.element);
                this.annotator.editor.checkOrientation();
            }
        },
398 399 400 401 402 403
        
        /**
         * This function will trigger the viewer to show up whenever an item is hovered
         * over and will cause the background color of the area to change a bit.
         * @param {Event} event The actual action of moving the mouse over an element.
         */
404 405 406
        _onAnnotationMouseMove: function(event){
            var annotator = this.annotator;
            var elem = jQuery(event.target).parents('.annotator-hl').andSelf();
407
            
408
            // if there is a opened annotation then show the new annotation mouse over
409
            if (typeof annotator!='undefined' && elem.hasClass("annotator-hl") && !this.isDrawing){
410
                // hide the last open viewer
411
                annotator.viewer.hide();
412
                // get the annotation over the mouse
413 414 415 416 417 418 419 420 421 422 423 424
                var annotations = jQuery(event.target.parentNode).find('.annotator-hl').map(function() {
                    var self = jQuery(this);
                    var offset = self.offset();
                    var l = offset.left;
                    var t = offset.top;
                    var h = self.height();
                    var w = self.width();
                    var x = $.getMousePosition(event).x;
                    var y = $.getMousePosition(event).y;

                    var maxx = l + w;
                    var maxy = t + h;
425
                    
426 427 428
                    // if the current position of the mouse is within the bounds of an area
                    // change the background of that area to a light yellow to simulate
                    // a hover. Otherwise, keep it translucent.
429
                    this.style.background = (y <= maxy && y >= t) && (x <= maxx && x >= l)?
430
                        'rgba(255, 255, 10, 0.05)':'rgba(0, 0, 0, 0)';
431
                    
432 433
                    return (y <= maxy && y >= t) && (x <= maxx && x >= l)? jQuery(this).data("annotation") : null;
                });
434
                // show the annotation in the viewer
435 436 437 438
                var mousePosition = {
                    top:$.getMousePosition(event).y,
                    left:$.getMousePosition(event).x,
                };
439 440
                // if the user is hovering over multiple annotation areas, 
                // they will be stacked as usual
441 442 443
                if (annotations.length>0) annotator.showViewer(jQuery.makeArray(annotations), mousePosition);
            }
        },
444 445 446 447 448
        
        /**
         * This function will zoom/pan the user into the bounding area of the annotation.
         * @param {Event} event The actual action of clicking down within an annotation area.
         */
449
        _onAnnotationClick: function(event){
450
            // gets the annotation from the data stored in the element
451
            var an = jQuery.data(event.target, 'annotation');
452
            // gets the bound within the annotation data
453 454
            var bounds = typeof an.bounds!='undefined'?an.bounds:{};
            var currentBounds = this.viewer.drawer.viewport.getBounds();
455
            // if the area is not already panned and zoomed in to the correct area
456 457 458 459
            if (typeof bounds.x!='undefined') currentBounds.x = bounds.x;
            if (typeof bounds.y!='undefined') currentBounds.y = bounds.y;
            if (typeof bounds.width!='undefined') currentBounds.width = bounds.width;
            if (typeof bounds.height!='undefined') currentBounds.height = bounds.height;
460
            // change the zoom to the saved parameter
461 462
            this.viewer.drawer.viewport.fitBounds(currentBounds);
        },
463
        
464
        /* Utilities */
465 466 467 468 469
        /**
         * This function will return an array of sorted items
         * @param {Array} annotations List of annotations from annotator instance.
         * @param {String} type Either 'asc' for ascending or 'desc' for descending
         */
470
        _sortByDate: function (annotations,type){
471
            var type = type || 'asc'; // asc => The value [0] will be the most recent date
472
            annotations.sort(function(a,b){
473
                // gets the date from when they were last updated
474 475
                a = new Date(typeof a.updated!='undefined'?createDateFromISO8601(a.updated):'');
                b = new Date(typeof b.updated!='undefined'?createDateFromISO8601(b.updated):'');
476
                
477
                // orders them based on type passed in
478 479 480 481 482 483
                if (type == 'asc')
                    return b<a?-1:b>a?1:0;
                else
                    return a<b?-1:a>b?1:0;
            });
        },
484 485 486 487
        /**
         * This function creates the button that will switch back and forth between
         * annotation creation mode and panning/zooming mode.
         */
488 489 490 491 492
        _createNewButton:function(){
            var viewer = this.viewer;
            var onFocusHandler          = $.delegate( this, onFocus );
            var onBlurHandler           = $.delegate( this, onBlur );
            var onModeAnnotationHandler  = $.delegate( this, this.modeAnnotation );
493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508
            /* Buttons */
            var viewer = this.viewer;
            var self = this;
            viewer.modeAnnotation = new $.Button({
                element:    viewer.modeAnnotation ? $.getElement( viewer.modeAnnotation ) : null,
                clickTimeThreshold: viewer.clickTimeThreshold,
                clickDistThreshold: viewer.clickDistThreshold,
                tooltip:    "New Annotation",
                srcRest:    self.resolveUrl( viewer.prefixUrl,"newan_rest.png"),
                srcGroup:      self.resolveUrl( viewer.prefixUrl,"newan_grouphover.png"),
                srcHover:   self.resolveUrl( viewer.prefixUrl,"newan_hover.png"),
                srcDown:    self.resolveUrl( viewer.prefixUrl,"newan_pressed.png"),
                onRelease:  onModeAnnotationHandler,
                onFocus:    onFocusHandler,
                onBlur:     onBlurHandler
            });
509 510 511 512 513 514 515 516 517
            
            //- Wrapper Annotation Menu
            viewer.wrapperAnnotation = new $.ButtonGroup({
                buttons: [
                    viewer.modeAnnotation,
                ],
                clickTimeThreshold: viewer.clickTimeThreshold,
                clickDistThreshold: viewer.clickDistThreshold
            });
518
            
519 520
            // area makes sure that the annotation button only appears when everyone is
            // allowed to annotate or if you are an instructor
lduarte1991 committed
521
            if(this.options.viewer.annotation_mode == "everyone" || this.options.viewer.flags){
522 523 524 525 526 527 528 529 530 531 532 533 534
                /* Set elements to the control menu */
                viewer.annotatorControl  = viewer.wrapperAnnotation.element;
                if( viewer.toolbar ){
                    viewer.toolbar.addControl(
                        viewer.annotatorControl,
                        {anchor: $.ControlAnchor.BOTTOM_RIGHT}
                    );
                }else{
                    viewer.addControl(
                        viewer.annotatorControl,
                        {anchor: $.ControlAnchor.TOP_LEFT}
                    );
                }
535 536
            }
        },
537 538 539 540 541
        
        /**
         * This function makes sure that if you're switching to panning/zooming mode,
         * the last rectangle you drew (but didn't save) gets destroyed.
         */
542
        _reset: function(){
543
            // Find and remove DrawingRect. This is the previous rectangle
544
            this._removeElemsByClass('DrawingRect',this.viewer.canvas);
545
            
546
            // Show adder and hide editor
547 548
            this.annotator.editor.element[0].style.display = 'none';
        },
549 550 551 552 553 554
        
        /**
         * This function binds the function to the object it was created from
         * @param {function} fn This is the function you want to apply
         * @param {Object} me This is the object you should pass it to (usually itself)
         */
555
        __bind: function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
556 557 558 559 560 561 562
        
        /**
         * Remove all the elements with a given name inside "inElement" to maintain
         * a limited scope.
         * @param {String} className Class that should be removed
         * @param {HTMLElement} inElement Element in which classes should be removed
         */
563 564 565 566 567 568 569 570
        _removeElemsByClass: function(className,inElement){
            var className = className || '';
            var inElement = inElement || {};
            divs = inElement.getElementsByClassName(className);
            for(var i = 0; i < divs.length; i++) {
                divs[i].remove();
            }
        },
571 572 573 574 575 576
        
        /** 
         * Detect if the annotation is an image annotation and there's a target, open
         * OSD instance.
         * @param {Object} an Annotation from the Annotator instance
         */
577 578 579
        _isOpenSeaDragon: function (an){
            var annotator = this.annotator;
            var rp = an.rangePosition;
580
            
581 582
            // Makes sure OSD exists and that annotation is an image annotation
            // with a position in the OSD instance
583 584 585 586 587
            var isOpenSeaDragon = (typeof annotator.osda != 'undefined');
            var isContainer = (typeof an.target!='undefined' && an.target.container==this.viewer.id );
            var isImage = (typeof an.media!='undefined' && an.media=='image');
            var isRP = (typeof rp!='undefined');
            var isSource = false;
588
            
589 590 591 592
            // Though it would be better to store server ids of images in the annotation that
            // would require changing annotations that were already made, instead we check if
            // the id is a substring of the thumbnail, which according to OpenSeaDragon API should be the case.
            var sourceId = this.viewer.source['@id'];
593 594 595 596 597 598 599 600 601

            // code runs on annotation creation before thumbnail is created
            var targetThumb = an.target ? an.target.thumb : false;
            if (isContainer) {
                // reason why this is okay is that we are trying to ascertain that the annotation
                // is related to the image drawn. If thumbnail attribute is empty it means the annotation
                // was just created and should still be considered an annotation of this image.
                isSource = targetThumb ? (targetThumb.indexOf(sourceId) !== -1) : true;
            }            
602 603
            return (isOpenSeaDragon && isContainer && isImage && isRP && isSource);
        },
604
        
605
        /* Annotator Utilities */
606 607 608 609 610
        /**
         * Makes sure that absolute x and y values for overlayed section are
         * calculated to match area within OSD instance
         * @param {HTMLElement} elem Element where shape is overlayed
         */
611
        _setOverShape: function(elem){
612
            // Calculate Point absolute positions 
613 614
            var rectPosition = this.rectPosition || {};
            var startPoint = this._logicalToPhysicalXY(new $.Point(rectPosition.left,rectPosition.top));
615
            var endPoint = this._logicalToPhysicalXY(new $.Point(rectPosition.left + rectPosition.width,rectPosition.top + rectPosition.height));
616
            
617
            // Calculate Point absolute positions    
618 619 620 621 622
            var wrapper = jQuery('.annotator-wrapper')[0];
            var positionAnnotator = $.getElementPosition(wrapper);
            var positionCanvas = $.getElementPosition(this.viewer.canvas);
            var positionAdder = {};
            
623
            // Fix with positionCanvas based on annotator wrapper and OSD area
624 625 626
            startPoint = startPoint.plus(positionCanvas);
            endPoint = endPoint.plus(positionCanvas);
            
627
            elem[0].style.display = 'block'; // Show the adder
628
        
629
            positionAdder.left = (startPoint.x - positionAnnotator.x) + (endPoint.x - startPoint.x) / 2;
630
            positionAdder.top =  (startPoint.y - positionAnnotator.y) + (endPoint.y - startPoint.y) / 2; // It is not necessary fix with - positionAnnotator.y
631 632
            elem.css(positionAdder);
        },
633
        
634 635 636
        resolveUrl: function( prefix, url ) {
            return prefix ? prefix + url : url;
        },
637
        
638
        /* Canvas Utilities */
639 640 641 642 643 644
        /**
         * Given a point of x and y values in pixels it will return a point with
         * percentages in relation to the Image object
         * @param {$.Point} point Canvas relative coordinates in pixels
         * @return {$.Point} Returns Image relative percentages
         */
645 646 647 648 649 650 651 652 653 654 655 656 657 658
        _physicalToLogicalXY: function(point){
            var point = typeof point!='undefined'?point:{};
            var boundX = this.viewer.viewport.getBounds(true).x;
            var boundY = this.viewer.viewport.getBounds(true).y;
            var boundWidth = this.viewer.viewport.getBounds(true).width;
            var boundHeight = this.viewer.viewport.getBounds(true).height;
            var containerSizeX = this.viewer.viewport.getContainerSize().x;
            var containerSizeY = this.viewer.viewport.getContainerSize().y;
            var x = typeof point.x!='undefined'?point.x:0;
            var y = typeof point.y!='undefined'?point.y:0;
            x = boundX + ((x / containerSizeX) * boundWidth);
            y = boundY + ((y / containerSizeY) * boundHeight);
            return new $.Point(x,y);
        },
659 660 661 662 663 664 665
        
        /**
         * Given values in percentage relatives to the image it will return a point in
         * pixels related to the canvas element.
         * @param {$.Point} point Image relative percentages
         * @return {$.Point} Returns canvas relative coordinates in pixels
         */
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682
        _logicalToPhysicalXY: function(point){
            var point = typeof point!='undefined'?point:{};
            var boundX = this.viewer.viewport.getBounds(true).x;
            var boundY = this.viewer.viewport.getBounds(true).y;
            var boundWidth = this.viewer.viewport.getBounds(true).width;
            var boundHeight = this.viewer.viewport.getBounds(true).height;
            var containerSizeX = this.viewer.viewport.getContainerSize().x;
            var containerSizeY = this.viewer.viewport.getContainerSize().y;
            var x = typeof point.x!='undefined'?point.x:0;
            var y = typeof point.y!='undefined'?point.y:0;
            x = (x - boundX) * containerSizeX / boundWidth;
            y = (y - boundY) * containerSizeY / boundHeight;
            return new $.Point(x,y);
        },
    }
    
    /* General functions */
683 684 685
    /**
     * initiates an animation to hide the controls
     */
686 687 688 689 690 691 692 693 694 695 696 697 698
    function beginControlsAutoHide( viewer ) {
        if ( !viewer.autoHideControls ) {
            return;
        }
        viewer.controlsShouldFade = true;
        viewer.controlsFadeBeginTime =
            $.now() +
            viewer.controlsFadeDelay;

        window.setTimeout( function(){
            scheduleControlsFade( viewer );
        }, viewer.controlsFadeDelay );
    }
699 700 701
    /**
     * stop the fade animation on the controls and show them
     */
702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717
    function abortControlsAutoHide( viewer ) {
        var i;
        viewer.controlsShouldFade = false;
        for ( i = viewer.controls.length - 1; i >= 0; i-- ) {
            viewer.controls[ i ].setOpacity( 1.0 );
        }
    }
    function onFocus(){
        abortControlsAutoHide( this.viewer );
    }

    function onBlur(){
        beginControlsAutoHide( this.viewer );
    }
    
    
718 719 720 721 722 723 724
})(OpenSeadragon);



//----------------Plugin for Annotator to setup OpenSeaDragon----------------//

Annotator.Plugin.OpenSeaDragon = (function(_super) {
725 726
    __extends(OpenSeaDragon, _super);

727 728 729 730
    /**
     * Creates an instance of the plugin that interacts with OpenSeaDragon.
     * @constructor
     */
731 732 733
    function OpenSeaDragon() {
        this.pluginSubmit = __bind(this.pluginSubmit, this);
        _ref = OpenSeaDragon.__super__.constructor.apply(this, arguments);
734
        
735
        // To facilitate calling items, we want to be able to get the index of a value 
736 737
        this.__indexOf = [].indexOf; 
        if(!this.__indexOf){
738
        
739 740
            // Basically you iterate through every item on the list, if it matches
            // the item you are looking for return the current index, otherwise return -1
741 742 743 744 745 746 747
            this.__indexOf = function(item) { 
                for (var i = 0, l = this.length; i < l; i++) { 
                    if (i in this && this[i] === item) 
                        return i; 
                } 
                return -1; 
            }
748 749 750 751 752 753 754

        }
        return _ref;
    }

    OpenSeaDragon.prototype.field = null;
    OpenSeaDragon.prototype.input = null;
755 756 757 758 759
    
    /**
     * This function initiates the editor that will apear when you edit/create an
     * annotation and the viewer that appears when you hover over an item.
     */
760
    OpenSeaDragon.prototype.pluginInit = function() {
761
        // Check that annotator is working
762 763 764 765 766 767 768
        if (!Annotator.supported()) {
            return;
        }
        
        //-- Editor
        this.field = this.annotator.editor.addField({
            id: 'osd-input-rangePosition-annotations',
769
            type: 'input', // options (textarea,input,select,checkbox)
770 771 772 773
            submit: this.pluginSubmit,
            EditOpenSeaDragonAn: this.EditOpenSeaDragonAn
        });
        
774
        // Modify the element created with annotator to be an invisible span
775 776 777 778 779 780 781 782 783 784 785
        var select = '<li><span id="osd-input-rangePosition-annotations"></span></li>';
        var newfield = Annotator.$(select);
        Annotator.$(this.field).replaceWith(newfield);
        this.field=newfield[0];

        //-- Listener for OpenSeaDragon Plugin
        this.initListeners();
        
        return this.input = $(this.field).find(':input');
    }
    
786 787 788 789 790
    /**
     * This function is called by annotator whenever user hits the "Save" Button. It will
     * first check to see if the user is editing or creating and then save the
     * metadata for the image in an object that will be passed to the backend. 
     */
791
    OpenSeaDragon.prototype.pluginSubmit = function(field, annotation) {
792
        // Select the new JSON for the Object to save
793 794 795
        if (this.EditOpenSeaDragonAn()){
            var annotator = this.annotator;
            var osda = annotator.osda;
796
            var position = osda.rectPosition || {};
797
            var isNew = typeof annotation.media=='undefined';
798
            if(isNew){
799 800
                // if it's undefined, we know it's an image because the editor within
                // the OSD instance was open
801 802 803
                if (typeof annotation.media == 'undefined') annotation.media = "image"; // - media
                annotation.target = annotation.target || {}; // - target
                annotation.target.container = osda.viewer.id || ""; // - target.container
804
                
805
                // Save source url
806 807
                var source = osda.viewer.source;
                var tilesUrl = typeof source.tilesUrl!='undefined'?source.tilesUrl:'';
808
                var functionUrl = typeof source.getTileUrl!='undefined'?source.getTileUrl():'';
809
                annotation.target.src = tilesUrl!=''?tilesUrl:('' + functionUrl).replace(/\s+/g, ' '); // - target.src (media source)
810
                annotation.target.ext = source.fileFormat || ""; // - target.ext (extension)
811
                
812
                // Gets the bounds in order to save them for zooming in and highlight properties
813
                annotation.bounds = osda.viewer.drawer.viewport.getBounds() || {}; // - bounds
814
                var finalimagelink = source["@id"].replace("/info.json", "");
815 816 817 818
                var highlightX = Math.round(position.left * source["width"]);
                var highlightY = Math.round(position.top * source["width"]);
                var highlightWidth = Math.round(position.width * source["width"]);
                var highlightHeight = Math.round(position.height * source["width"]);
819
                
820 821
                // creates a link to the OSD server that contains the image to get
                // the thumbnail of the selected portion of the image
822 823
                annotation.target.thumb = finalimagelink + "/" + highlightX + "," + highlightY + "," + highlightWidth + "," + highlightHeight + "/full/0/native." + source["formats"][0];
                if(isNew) annotation.rangePosition =     position || {};    // - rangePosition
824
                
825
                // updates the dates associated with creation and update
826 827 828 829
                annotation.updated = new Date().toISOString(); // - updated
                if (typeof annotation.created == 'undefined')
                    annotation.created = annotation.updated; // - created
            }
830 831 832 833 834 835
        }
        return annotation.media;
    };
    
    
    //------ Methods    ------//
836 837 838
    /**
     * Detect if we are creating or editing an OpenSeaDragon annotation
     */
839 840 841 842 843 844 845 846
    OpenSeaDragon.prototype.EditOpenSeaDragonAn =  function (){
        var wrapper = $('.annotator-wrapper').parent()[0],
            annotator = window.annotator = $.data(wrapper, 'annotator'),
            isOpenSeaDragon = (typeof annotator.osda != 'undefined'),
            OpenSeaDragon = annotator.editor.OpenSeaDragon;
        return (isOpenSeaDragon && typeof OpenSeaDragon!='undefined' && OpenSeaDragon!==-1);
    };
    
847
    /** 
848 849 850 851
     * Detect if the annotation is an image annotation and there's a target, open
     * OSD instance.
     * @param {Object} an Annotation from the Annotator instance
     */
852
    OpenSeaDragon.prototype.isOpenSeaDragon = function (an){
853
        var annotator = this.annotator;
854 855 856 857 858
        var rp = an.rangePosition;
        
        // Makes sure OSD exists and that annotation is an image annotation
        // with a position in the OSD instance
        var isOpenSeaDragon = (typeof annotator.osda != 'undefined');
859
        var isContainer = (typeof an.target!='undefined' && an.target.container==osda.viewer.id );
860 861 862 863 864
        var isImage = (typeof an.media!='undefined' && an.media=='image');
        var isRP = (typeof rp!='undefined');
        var isSource = false;
        
        // Double checks that the image being displayed matches the annotations 
865
        var source = osda.viewer.source;
866 867 868 869 870 871
        var tilesUrl = typeof source.tilesUrl!='undefined'?source.tilesUrl:'';
        var functionUrl = typeof source.getTileUrl!='undefined'?source.getTileUrl:'';
        var compareUrl = tilesUrl!=''?tilesUrl:('' + functionUrl).replace(/\s+/g, ' ');
        if(isContainer) isSource = (an.target.src == compareUrl);
        
        return (isOpenSeaDragon && isContainer && isImage && isRP && isSource);
872 873
    };
    
874 875 876 877
    /**
     * Deletes the OSD annotation from Annotator and refreshes display to remove element
     * @param {Object} an Annotation object from the Annotator instance
     */
878
    OpenSeaDragon.prototype._deleteAnnotation = function(an){
879
        // Remove the annotation of the plugin Store
880
        var annotations = this.annotator.plugins['Store'].annotations;
881
        
882
        // Failsafe in case annotation is not immediately removed from annotations list
883 884
        if (annotations.indexOf(an)>-1)
            annotations.splice(annotations.indexOf(an), 1);
885
        
886
        // Refresh the annotations in the display
887 888 889 890 891 892 893 894 895 896 897 898
        this.annotator.osda.refreshDisplay();
    };
    
    
    //--Listeners
    OpenSeaDragon.prototype.initListeners = function (){
        var wrapper = $('.annotator-wrapper').parent()[0];
        var annotator = $.data(wrapper, 'annotator');
        var EditOpenSeaDragonAn = this.EditOpenSeaDragonAn;
        var isOpenSeaDragon = this.isOpenSeaDragon;
        var self = this;
            
899
        // local functions
900 901 902 903
        //-- Editor
        function annotationEditorHidden(editor) {
            if (EditOpenSeaDragonAn()){
                annotator.osda._reset();
904
                annotator.osda.refreshDisplay(); // Reload the display of annotations
905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920
            }
            annotator.editor.OpenSeaDragon=-1;
            annotator.unsubscribe("annotationEditorHidden", annotationEditorHidden);
        };
        function annotationEditorShown(editor,annotation) {
            annotator.osda.editAnnotation(annotation,editor);
            annotator.subscribe("annotationEditorHidden", annotationEditorHidden);
        };
        //-- Annotations
        function annotationDeleted(annotation) {
            if (isOpenSeaDragon(annotation))
                self._deleteAnnotation(annotation);
        };
        //-- Viewer
        function hideViewer(){
            jQuery(annotator.osda.viewer.canvas.parentNode).find('.annotator-hl').map(function() {
921
                return this.style.background = 'rgba(0, 0, 0, 0)';
922 923 924 925 926 927
            });
            annotator.viewer.unsubscribe("hide", hideViewer);
        };
        function annotationViewerShown(viewer,annotations) {
            var wrapper = jQuery('.annotator-wrapper').offset();

928
            // Fix with positionCanvas
929 930 931 932 933 934 935 936 937 938
            var startPoint = {x: parseFloat(viewer.element[0].style.left),
                y: parseFloat(viewer.element[0].style.top)};
        
            var separation = viewer.element.hasClass(viewer.classes.invert.y)?5:-5,
                newpos = {
                    top: (startPoint.y - wrapper.top)+separation,
                    left: (startPoint.x - wrapper.left)
                };
            viewer.element.css(newpos);
            
939
            // Remove the time to wait until disapear, to be more faster that annotator by default
940 941 942 943
            viewer.element.find('.annotator-controls').removeClass(viewer.classes.showControls);
            
            annotator.viewer.subscribe("hide", hideViewer);
        };    
944
        // subscribe to Annotator
945 946 947 948 949 950
        annotator.subscribe("annotationEditorShown", annotationEditorShown)
            .subscribe("annotationDeleted", annotationDeleted)
            .subscribe("annotationViewerShown", annotationViewerShown);
    }

    return OpenSeaDragon;
951 952 953 954 955 956 957

})(Annotator.Plugin);



//----------------PUBLIC OBJECT TO CONTROL THE ANNOTATIONS----------------//

958
// The name of the plugin that the user will write in the html
959 960 961
OpenSeadragonAnnotation = ("OpenSeadragonAnnotation" in window) ? OpenSeadragonAnnotation : {};

OpenSeadragonAnnotation = function (element, options) {
962
    // local variables
963 964 965 966 967 968
    var $ = jQuery;
    var options = options || {};
    options.optionsOpenSeadragon = options.optionsOpenSeadragon || {};
    options.optionsOSDA = options.optionsOSDA || {};
    options.optionsAnnotator = options.optionsAnnotator || {};
    
969
    // if there isn't store optinos it will create a uri and limit variables for the Back-end of Annotations 
970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985
    if (typeof options.optionsAnnotator.store=='undefined')
        options.optionsAnnotator.store = {};
    var store = options.optionsAnnotator.store;
    if (typeof store.annotationData=='undefined')
        store.annotationData = {};
    if (typeof store.annotationData.uri=='undefined'){
        var uri = location.protocol + '//' + location.host + location.pathname;
        store.annotationData.store = {uri:uri};
    }
    if (typeof store.loadFromSearch=='undefined')
        store.loadFromSearch={};
    if (typeof store.loadFromSearch.uri=='undefined')
        store.loadFromSearch.uri = uri;
    if (typeof store.loadFromSearch.limit=='undefined')
        store.loadFromSearch.limit = 10000;
        
986
    // global variables
987 988 989
    this.currentUser = null;

    //-- Init all the classes --/
990
    // Annotator
991 992 993 994 995 996 997 998 999 1000 1001 1002
    this.annotator = $(element).annotator(options.optionsAnnotator.annotator).data('annotator');
    
    //-- Activate all the Annotator plugins --//
    if (typeof options.optionsAnnotator.auth!='undefined')
        this.annotator.addPlugin('Auth', options.optionsAnnotator.auth);
        
    if (typeof options.optionsAnnotator.permissions!='undefined')
        this.annotator.addPlugin("Permissions", options.optionsAnnotator.permissions);
    
    if (typeof options.optionsAnnotator.store!='undefined')
        this.annotator.addPlugin("Store", options.optionsAnnotator.store);
            
1003
    if (typeof Annotator.Plugin["Geolocation"] === 'function') 
1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016
        this.annotator.addPlugin("Geolocation",options.optionsAnnotator.geolocation);
        
    if (typeof Annotator.Plugin["Share"] === 'function') 
        this.annotator.addPlugin("Share",options.optionsAnnotator.share);
        
    if (typeof Annotator.Plugin["RichText"] === 'function') 
        this.annotator.addPlugin("RichText",options.optionsAnnotator.richText);
        
    if (typeof Annotator.Plugin["Reply"] === 'function') 
        this.annotator.addPlugin("Reply");
        
    if (typeof Annotator.Plugin["OpenSeaDragon"] === 'function') 
        this.annotator.addPlugin("OpenSeaDragon");
1017 1018
            
    if (typeof Annotator.Plugin["Flagging"] === 'function') 
1019
        this.annotator.addPlugin("Flagging");
1020

1021 1022
    if (typeof Annotator.Plugin["HighlightTags"] === 'function')
        this.annotator.addPlugin("HighlightTags", options.optionsAnnotator.highlightTags);
1023

1024 1025 1026
    //- OpenSeaDragon
    this.viewer =  OpenSeadragon(options.optionsOpenSeadragon);
    //- OpenSeaDragon Plugins
1027 1028
    this.viewer.annotation(options.optionsOSDA);
    
1029
    // Set annotator.editor.OpenSeaDragon by default
1030
    this.annotator.editor.OpenSeaDragon=-1;
1031
    
1032
    // We need to make sure that osda is accessible via annotator
1033
    this.annotator.osda = this;
1034 1035

    function reloadEditor(){
1036 1037
        tinymce.EditorManager.execCommand('mceRemoveEditor',true, "annotator-field-0");
        tinymce.EditorManager.execCommand('mceAddEditor',true, "annotator-field-0");
1038 1039 1040 1041
        
        // if person hits into/out of fullscreen before closing the editor should close itself
        // ideally we would want to keep it open and reposition, this would make a great TODO in the future
        annotator.editor.hide();
1042 1043 1044 1045
    }

    var self = this;
    document.addEventListener("fullscreenchange", function () {
1046 1047
        reloadEditor();
    }, false);
1048
 
1049 1050 1051
    document.addEventListener("mozfullscreenchange", function () {
        reloadEditor();
    }, false);
1052
 
1053 1054 1055
    document.addEventListener("webkitfullscreenchange", function () {
        reloadEditor();
    }, false);
1056
 
1057 1058
    document.addEventListener("msfullscreenchange", function () {
        reloadEditor();
1059
    }, false);
1060 1061 1062 1063 1064 1065 1066 1067

    // for some reason the above doesn't work when person hits ESC to exit full screen...
    $(document).keyup(function(e) {
        // esc key reloads editor as well
        if (e.keyCode == 27) { 
            reloadEditor();
        }   
    });
1068 1069 1070
    
    this.options = options;

1071
    return this;
1072 1073 1074 1075
}