Subversion Repositories Applications.framework

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
467 jpm 1
/*
2
 * iviewer Widget for jQuery UI
3
 * https://github.com/can3p/iviewer
4
 *
5
 * Copyright (c) 2009 - 2012 Dmitry Petrov
6
 * Dual licensed under the MIT and GPL licenses.
7
 *  - http://www.opensource.org/licenses/mit-license.php
8
 *  - http://www.gnu.org/copyleft/gpl.html
9
 *
10
 * Author: Dmitry Petrov
11
 * Version: 0.7.7
12
 */
13
 
14
( function( $, undefined ) {
15
 
16
//this code was taken from the https://github.com/furf/jquery-ui-touch-punch
17
var mouseEvents = {
18
        touchstart: 'mousedown',
19
        touchmove: 'mousemove',
20
        touchend: 'mouseup'
21
    },
22
    gesturesSupport = 'ongesturestart' in document.createElement('div');
23
 
24
 
25
/**
26
 * Convert a touch event to a mouse-like
27
 */
28
function makeMouseEvent (event) {
29
    var touch = event.originalEvent.changedTouches[0];
30
 
31
    return $.extend(event, {
32
        type:    mouseEvents[event.type],
33
        which:   1,
34
        pageX:   touch.pageX,
35
        pageY:   touch.pageY,
36
        screenX: touch.screenX,
37
        screenY: touch.screenY,
38
        clientX: touch.clientX,
39
        clientY: touch.clientY,
40
        isTouchEvent: true
41
    });
42
}
43
 
44
var mouseProto = $.ui.mouse.prototype,
45
    _mouseInit = $.ui.mouse.prototype._mouseInit;
46
 
47
mouseProto._mouseInit = function() {
48
    var self = this;
49
    self._touchActive = false;
50
 
51
    this.element.bind( 'touchstart.' + this.widgetName, function(event) {
52
        if (gesturesSupport && event.originalEvent.touches.length > 1) { return; }
53
        self._touchActive = true;
54
        return self._mouseDown(makeMouseEvent(event));
55
    })
56
 
57
    var self = this;
58
    // these delegates are required to keep context
59
    this._mouseMoveDelegate = function(event) {
60
        if (gesturesSupport && event.originalEvent.touches && event.originalEvent.touches.length > 1) { return; }
61
        if (self._touchActive) {
62
            return self._mouseMove(makeMouseEvent(event));
63
        }
64
    };
65
    this._mouseUpDelegate = function(event) {
66
        if (self._touchActive) {
67
            self._touchActive = false;
68
            return self._mouseUp(makeMouseEvent(event));
69
        }
70
    };
71
 
72
    $(document)
73
        .bind('touchmove.'+ this.widgetName, this._mouseMoveDelegate)
74
        .bind('touchend.' + this.widgetName, this._mouseUpDelegate);
75
 
76
    _mouseInit.apply(this);
77
}
78
 
79
/**
80
 * Simple implementation of jQuery like getters/setters
81
 * var val = something();
82
 * something(val);
83
 */
84
var setter = function(setter, getter) {
85
    return function(val) {
86
        if (arguments.length === 0) {
87
            return getter.apply(this);
88
        } else {
89
            setter.apply(this, arguments);
90
        }
91
    }
92
};
93
 
94
/**
95
 * Internet explorer rotates image relative left top corner, so we should
96
 * shift image when it's rotated.
97
 */
98
var ieTransforms = {
99
        '0': {
100
            marginLeft: 0,
101
            marginTop: 0,
102
            filter: 'progid:DXImageTransform.Microsoft.Matrix(M11=1, M12=0, M21=0, M22=1, SizingMethod="auto expand")'
103
        },
104
 
105
        '90': {
106
            marginLeft: -1,
107
            marginTop: 1,
108
            filter: 'progid:DXImageTransform.Microsoft.Matrix(M11=0, M12=-1, M21=1, M22=0, SizingMethod="auto expand")'
109
        },
110
 
111
        '180': {
112
            marginLeft: 0,
113
            marginTop: 0,
114
            filter: 'progid:DXImageTransform.Microsoft.Matrix(M11=-1, M12=0, M21=0, M22=-1, SizingMethod="auto expand")'
115
        },
116
 
117
        '270': {
118
            marginLeft: -1,
119
            marginTop: 1,
120
            filter: 'progid:DXImageTransform.Microsoft.Matrix(M11=0, M12=1, M21=-1, M22=0, SizingMethod="auto expand")'
121
        }
122
    },
123
    // this test is the inversion of the css filters test from the modernizr project
124
    useIeTransforms = function() {
125
        var modElem = document.createElement('modernizr'),
126
		mStyle = modElem.style,
127
		omPrefixes = 'Webkit Moz O ms',
128
		domPrefixes = omPrefixes.toLowerCase().split(' '),
129
        	props = ("transform" + ' ' + domPrefixes.join("Transform ") + "Transform").split(' ');
130
        for ( var i in props ) {
131
            var prop = props[i];
132
            if ( !$.contains(prop, "-") && mStyle[prop] !== undefined ) {
133
                return false;
134
            }
135
        }
136
        return true;
137
    }();
138
 
139
$.widget( "ui.iviewer", $.ui.mouse, {
140
    widgetEventPrefix: "iviewer",
141
    options : {
142
        /**
143
        * start zoom value for image, not used now
144
        * may be equal to "fit" to fit image into container or scale in %
145
        **/
146
        zoom: "fit",
147
        /**
148
        * base value to scale image
149
        **/
150
        zoom_base: 100,
151
        /**
152
        * maximum zoom
153
        **/
154
        zoom_max: 800,
155
        /**
156
        * minimum zoom
157
        **/
158
        zoom_min: 25,
159
        /**
160
        * base of rate multiplier.
161
        * zoom is calculated by formula: zoom_base * zoom_delta^rate
162
        **/
163
        zoom_delta: 1.4,
164
        /**
165
        * whether the zoom should be animated.
166
        */
167
        zoom_animation: true,
168
        /**
169
        * if true plugin doesn't add its own controls
170
        **/
171
        ui_disabled: false,
172
        /**
173
         * If false mousewheel will be disabled
174
         */
175
        mousewheel: true,
176
        /**
177
        * if false, plugin doesn't bind resize event on window and this must
178
        * be handled manually
179
        **/
180
        update_on_resize: true,
181
        /**
182
        * event is triggered when zoom value is changed
183
        * @param int new zoom value
184
        * @return boolean if false zoom action is aborted
185
        **/
186
        onZoom: jQuery.noop,
187
        /**
188
        * event is triggered when zoom value is changed after image is set to the new dimensions
189
        * @param int new zoom value
190
        * @return boolean if false zoom action is aborted
191
        **/
192
        onAfterZoom: jQuery.noop,
193
        /**
194
        * event is fired on drag begin
195
        * @param object coords mouse coordinates on the image
196
        * @return boolean if false is returned, drag action is aborted
197
        **/
198
        onStartDrag: jQuery.noop,
199
        /**
200
        * event is fired on drag action
201
        * @param object coords mouse coordinates on the image
202
        **/
203
        onDrag: jQuery.noop,
204
        /**
205
        * event is fired on drag stop
206
        * @param object coords mouse coordinates on the image
207
        **/
208
        onStopDrag: jQuery.noop,
209
        /**
210
        * event is fired when mouse moves over image
211
        * @param object coords mouse coordinates on the image
212
        **/
213
        onMouseMove: jQuery.noop,
214
        /**
215
        * mouse click event
216
        * @param object coords mouse coordinates on the image
217
        **/
218
        onClick: jQuery.noop,
219
        /**
220
        * event is fired when image starts to load
221
        */
222
        onStartLoad: null,
223
        /**
224
        * event is fired, when image is loaded and initially positioned
225
        */
226
        onFinishLoad: null,
227
        /**
228
        * event is fired when image load error occurs
229
        */
230
        onErrorLoad: null
231
    },
232
 
233
    _create: function() {
234
        var me = this;
235
 
236
        //drag variables
237
        this.dx = 0;
238
        this.dy = 0;
239
 
240
        /* object containing actual information about image
241
        *   @img_object.object - jquery img object
242
        *   @img_object.orig_{width|height} - original dimensions
243
        *   @img_object.display_{width|height} - actual dimensions
244
        */
245
        this.img_object = {};
246
 
247
        this.zoom_object = {}; //object to show zoom status
248
 
249
        this._angle = 0;
250
 
251
        this.current_zoom = this.options.zoom;
252
 
253
        if(this.options.src === null){
254
            return;
255
        }
256
 
257
        this.container = this.element;
258
 
259
        this._updateContainerInfo();
260
 
261
        //init container
262
        this.container.css("overflow","hidden");
263
 
264
        if (this.options.update_on_resize == true) {
265
            $(window).resize(function() {
266
                me.update();
267
            });
268
        }
269
 
270
        this.img_object = new $.ui.iviewer.ImageObject(this.options.zoom_animation);
271
 
272
        if (this.options.mousewheel) {
273
            this.container.bind('mousewheel.iviewer', function(ev, delta)
274
                {
275
                    //this event is there instead of containing div, because
276
                    //at opera it triggers many times on div
277
                    var zoom = (delta > 0)?1:-1,
278
                        container_offset = me.container.offset(),
279
                        mouse_pos = {
280
                            //jquery.mousewheel 3.1.0 uses strange MozMousePixelScroll event
281
                            //which is not being fixed by jQuery.Event
282
                            x: (ev.pageX || ev.originalEvent.pageX) - container_offset.left,
283
                            y: (ev.pageY || ev.originalEvent.pageX) - container_offset.top
284
                        };
285
 
286
                    me.zoom_by(zoom, mouse_pos);
287
                    return false;
288
                });
289
 
290
            if (gesturesSupport) {
291
                var gestureThrottle = +new Date();
292
                var originalScale, originalCenter;
293
                this.img_object.object()
294
                    // .bind('gesturestart', function(ev) {
295
                    .bind('touchstart', function(ev) {
296
                        originalScale = me.current_zoom;
297
                        var touches = ev.originalEvent.touches,
298
                            container_offset;
299
                        if (touches.length == 2) {
300
                            container_offset = me.container.offset();
301
                            originalCenter = {
302
                                x: (touches[0].pageX + touches[1].pageX) / 2  - container_offset.left,
303
                                y: (touches[0].pageY + touches[1].pageY) / 2 - container_offset.top
304
                            };
305
                        } else {
306
                            originalCenter = null;
307
                        }
308
                    }).bind('gesturechange', function(ev) {
309
                        //do not want to import throttle function from underscore
310
                        var d = +new Date();
311
                        if ((d - gestureThrottle) < 50) { return; }
312
                        gestureThrottle = d;
313
                        var zoom = originalScale * ev.originalEvent.scale;
314
                        me.set_zoom(zoom, originalCenter);
315
                        ev.preventDefault();
316
                    }).bind('gestureend', function(ev) {
317
                        originalCenter = null;
318
                    });
319
            }
320
        }
321
 
322
        //init object
323
        this.img_object.object()
324
            //bind mouse events
325
            .click(function(e){return me._click(e)})
326
                .prependTo(this.container);
327
 
328
        this.container.bind('mousemove', function(ev) { me._handleMouseMove(ev); });
329
 
330
        this.loadImage(this.options.src);
331
 
332
        if(!this.options.ui_disabled)
333
        {
334
            this.createui();
335
        }
336
 
337
        this._mouseInit();
338
    },
339
 
340
    destroy: function() {
341
        $.Widget.prototype.destroy.call( this );
342
        this._mouseDestroy();
343
        this.img_object.object().remove();
344
        this.container.off('.iviewer');
345
        this.container.css('overflow', ''); //cleanup styles on destroy
346
    },
347
 
348
    _updateContainerInfo: function()
349
    {
350
        this.options.height = this.container.height();
351
        this.options.width = this.container.width();
352
    },
353
 
354
    update: function()
355
    {
356
        this._updateContainerInfo()
357
        this.setCoords(this.img_object.x(), this.img_object.y());
358
    },
359
 
360
    loadImage: function( src )
361
    {
362
        this.current_zoom = this.options.zoom;
363
        var me = this;
364
 
365
        this._trigger('onStartLoad', 0, src);
366
 
367
        this.container.addClass("iviewer_loading");
368
        this.img_object.load(src, function() {
369
            me._imageLoaded(src);
370
        }, function() {
371
            me._trigger("onErrorLoad", 0, src);
372
        });
373
    },
374
 
375
    _imageLoaded: function(src) {
376
        this.container.removeClass("iviewer_loading");
377
        this.container.addClass("iviewer_cursor");
378
 
379
        if(this.options.zoom == "fit"){
380
            this.fit(true);
381
        }
382
        else {
383
            this.set_zoom(this.options.zoom, true);
384
        }
385
 
386
        this._trigger('onFinishLoad', 0, src);
387
    },
388
 
389
    /**
390
    * fits image in the container
391
    *
392
    * @param {boolean} skip_animation
393
    **/
394
    fit: function(skip_animation)
395
    {
396
        var aspect_ratio = this.img_object.orig_width() / this.img_object.orig_height();
397
        var window_ratio = this.options.width /  this.options.height;
398
        var choose_left = (aspect_ratio > window_ratio);
399
        var new_zoom = 0;
400
 
401
        if(choose_left){
402
            new_zoom = this.options.width / this.img_object.orig_width() * 100;
403
        }
404
        else {
405
            new_zoom = this.options.height / this.img_object.orig_height() * 100;
406
        }
407
 
408
      this.set_zoom(new_zoom, skip_animation);
409
    },
410
 
411
    /**
412
    * center image in container
413
    **/
414
    center: function()
415
    {
416
        this.setCoords(-Math.round((this.img_object.display_width() - this.options.width)/2),
417
                -Math.round((this.img_object.display_height() - this.options.height)/2));
418
    },
419
 
420
    /**
421
    *   move a point in container to the center of display area
422
    *   @param x a point in container
423
    *   @param y a point in container
424
    **/
425
    moveTo: function(x, y)
426
    {
427
        var dx = x-Math.round(this.options.width/2);
428
        var dy = y-Math.round(this.options.height/2);
429
 
430
        var new_x = this.img_object.x() - dx;
431
        var new_y = this.img_object.y() - dy;
432
 
433
        this.setCoords(new_x, new_y);
434
    },
435
 
436
    /**
437
     * Get container offset object.
438
     */
439
    getContainerOffset: function() {
440
        return jQuery.extend({}, this.container.offset());
441
    },
442
 
443
    /**
444
    * set coordinates of upper left corner of image object
445
    **/
446
    setCoords: function(x,y)
447
    {
448
        //do nothing while image is being loaded
449
        if(!this.img_object.loaded()) { return; }
450
 
451
        var coords = this._correctCoords(x,y);
452
        this.img_object.x(coords.x);
453
        this.img_object.y(coords.y);
454
    },
455
 
456
    _correctCoords: function( x, y )
457
    {
458
        x = parseInt(x, 10);
459
        y = parseInt(y, 10);
460
 
461
        //check new coordinates to be correct (to be in rect)
462
        if(y > 0){
463
            y = 0;
464
        }
465
        if(x > 0){
466
            x = 0;
467
        }
468
        if(y + this.img_object.display_height() < this.options.height){
469
            y = this.options.height - this.img_object.display_height();
470
        }
471
        if(x + this.img_object.display_width() < this.options.width){
472
            x = this.options.width - this.img_object.display_width();
473
        }
474
        if(this.img_object.display_width() <= this.options.width){
475
            x = -(this.img_object.display_width() - this.options.width)/2;
476
        }
477
        if(this.img_object.display_height() <= this.options.height){
478
            y = -(this.img_object.display_height() - this.options.height)/2;
479
        }
480
 
481
        return { x: x, y:y };
482
    },
483
 
484
 
485
    /**
486
    * convert coordinates on the container to the coordinates on the image (in original size)
487
    *
488
    * @return object with fields x,y according to coordinates or false
489
    * if initial coords are not inside image
490
    **/
491
    containerToImage : function (x,y)
492
    {
493
        var coords = { x : x - this.img_object.x(),
494
                 y :  y - this.img_object.y()
495
        };
496
 
497
        coords = this.img_object.toOriginalCoords(coords);
498
 
499
        return { x :  util.descaleValue(coords.x, this.current_zoom),
500
                 y :  util.descaleValue(coords.y, this.current_zoom)
501
        };
502
    },
503
 
504
    /**
505
    * convert coordinates on the image (in original size, and zero angle) to the coordinates on the container
506
    *
507
    * @return object with fields x,y according to coordinates
508
    **/
509
    imageToContainer : function (x,y)
510
    {
511
        var coords = {
512
                x : util.scaleValue(x, this.current_zoom),
513
                y : util.scaleValue(y, this.current_zoom)
514
            };
515
 
516
        return this.img_object.toRealCoords(coords);
517
    },
518
 
519
    /**
520
    * get mouse coordinates on the image
521
    * @param e - object containing pageX and pageY fields, e.g. mouse event object
522
    *
523
    * @return object with fields x,y according to coordinates or false
524
    * if initial coords are not inside image
525
    **/
526
    _getMouseCoords : function(e)
527
    {
528
        var containerOffset = this.container.offset();
529
            coords = this.containerToImage(e.pageX - containerOffset.left, e.pageY - containerOffset.top);
530
 
531
        return coords;
532
    },
533
 
534
    /**
535
    * set image scale to the new_zoom
536
    *
537
    * @param {number} new_zoom image scale in %
538
    * @param {boolean} skip_animation
539
    * @param {x: number, y: number} Coordinates of point the should not be moved on zoom. The default is the center of image.
540
    **/
541
    set_zoom: function(new_zoom, skip_animation, zoom_center)
542
    {
543
        if (this._trigger('onZoom', 0, new_zoom) == false) {
544
            return;
545
        }
546
 
547
        //do nothing while image is being loaded
548
        if(!this.img_object.loaded()) { return; }
549
 
550
        zoom_center = zoom_center || {
551
            x: Math.round(this.options.width/2),
552
            y: Math.round(this.options.height/2)
553
        }
554
 
555
        if(new_zoom <  this.options.zoom_min)
556
        {
557
            new_zoom = this.options.zoom_min;
558
        }
559
        else if(new_zoom > this.options.zoom_max)
560
        {
561
            new_zoom = this.options.zoom_max;
562
        }
563
 
564
        /* we fake these values to make fit zoom properly work */
565
        if(this.current_zoom == "fit")
566
        {
567
            var old_x = zoom_center.x + Math.round(this.img_object.orig_width()/2);
568
            var old_y = zoom_center.y + Math.round(this.img_object.orig_height()/2);
569
            this.current_zoom = 100;
570
        }
571
        else {
572
            var old_x = -this.img_object.x() + zoom_center.x;
573
            var old_y = -this.img_object.y() + zoom_center.y
574
        }
575
 
576
        var new_width = util.scaleValue(this.img_object.orig_width(), new_zoom);
577
        var new_height = util.scaleValue(this.img_object.orig_height(), new_zoom);
578
        var new_x = util.scaleValue( util.descaleValue(old_x, this.current_zoom), new_zoom);
579
        var new_y = util.scaleValue( util.descaleValue(old_y, this.current_zoom), new_zoom);
580
 
581
        new_x = zoom_center.x - new_x;
582
        new_y = zoom_center.y - new_y;
583
 
584
        new_width = Math.floor(new_width);
585
        new_height = Math.floor(new_height);
586
        new_x = Math.floor(new_x);
587
        new_y = Math.floor(new_y);
588
 
589
        this.img_object.display_width(new_width);
590
        this.img_object.display_height(new_height);
591
 
592
        var coords = this._correctCoords( new_x, new_y ),
593
            self = this;
594
 
595
        this.img_object.setImageProps(new_width, new_height, coords.x, coords.y,
596
                                        skip_animation, function() {
597
            self._trigger('onAfterZoom', 0, new_zoom );
598
        });
599
        this.current_zoom = new_zoom;
600
 
601
        this.update_status();
602
    },
603
 
604
    /**
605
    * changes zoom scale by delta
606
    * zoom is calculated by formula: zoom_base * zoom_delta^rate
607
    * @param Integer delta number to add to the current multiplier rate number
608
    * @param {x: number, y: number=} Coordinates of point the should not be moved on zoom.
609
    **/
610
    zoom_by: function(delta, zoom_center)
611
    {
612
        var closest_rate = this.find_closest_zoom_rate(this.current_zoom);
613
 
614
        var next_rate = closest_rate + delta;
615
        var next_zoom = this.options.zoom_base * Math.pow(this.options.zoom_delta, next_rate)
616
        if(delta > 0 && next_zoom < this.current_zoom)
617
        {
618
            next_zoom *= this.options.zoom_delta;
619
        }
620
 
621
        if(delta < 0 && next_zoom > this.current_zoom)
622
        {
623
            next_zoom /= this.options.zoom_delta;
624
        }
625
 
626
        this.set_zoom(next_zoom, undefined, zoom_center);
627
    },
628
 
629
    /**
630
    * Rotate image
631
    * @param {num} deg Degrees amount to rotate. Positive values rotate image clockwise.
632
    *     Currently 0, 90, 180, 270 and -90, -180, -270 values are supported
633
    *
634
    * @param {boolean} abs If the flag is true if, the deg parameter will be considered as
635
    *     a absolute value and relative otherwise.
636
    * @return {num|null} Method will return current image angle if called without any arguments.
637
    **/
638
    angle: function(deg, abs) {
639
        if (arguments.length === 0) { return this.img_object.angle(); }
640
 
641
        if (deg < -270 || deg > 270 || deg % 90 !== 0) { return; }
642
        if (!abs) { deg += this.img_object.angle(); }
643
        if (deg < 0) { deg += 360; }
644
        if (deg >= 360) { deg -= 360; }
645
 
646
        if (deg === this.img_object.angle()) { return; }
647
 
648
        this.img_object.angle(deg);
649
        //the rotate behavior is different in all editors. For now we  just center the
650
        //image. However, it will be better to try to keep the position.
651
        this.center();
652
        this._trigger('angle', 0, { angle: this.img_object.angle() });
653
    },
654
 
655
    /**
656
    * finds closest multiplier rate for value
657
    * basing on zoom_base and zoom_delta values from settings
658
    * @param Number value zoom value to examine
659
    **/
660
    find_closest_zoom_rate: function(value)
661
    {
662
        if(value == this.options.zoom_base)
663
        {
664
            return 0;
665
        }
666
 
667
        function div(val1,val2) { return val1 / val2 };
668
        function mul(val1,val2) { return val1 * val2 };
669
 
670
        var func = (value > this.options.zoom_base)?mul:div;
671
        var sgn = (value > this.options.zoom_base)?1:-1;
672
 
673
        var mltplr = this.options.zoom_delta;
674
        var rate = 1;
675
 
676
        while(Math.abs(func(this.options.zoom_base, Math.pow(mltplr,rate)) - value) >
677
              Math.abs(func(this.options.zoom_base, Math.pow(mltplr,rate+1)) - value))
678
        {
679
            rate++;
680
        }
681
 
682
        return sgn * rate;
683
    },
684
 
685
    /* update scale info in the container */
686
    update_status: function()
687
    {
688
        if(!this.options.ui_disabled)
689
        {
690
            var percent = Math.round(100*this.img_object.display_height()/this.img_object.orig_height());
691
            if(percent)
692
            {
693
                this.zoom_object.html(percent + "%");
694
            }
695
        }
696
    },
697
 
698
    /**
699
     * Get some information about the image.
700
     *     Currently orig_(width|height), display_(width|height), angle, zoom and src params are supported.
701
     *
702
     *  @param {string} parameter to check
703
     *  @param {boolean} withoutRotation if param is orig_width or orig_height and this flag is set to true,
704
     *      method will return original image width without considering rotation.
705
     *
706
     */
707
    info: function(param, withoutRotation) {
708
        if (!param) { return; }
709
 
710
        switch (param) {
711
            case 'orig_width':
712
            case 'orig_height':
713
                if (withoutRotation) {
714
                    return (this.img_object.angle() % 180 === 0 ? this.img_object[param]() :
715
                            param === 'orig_width' ? this.img_object.orig_height() :
716
                                                        this.img_object.orig_width());
717
                } else {
718
                    return this.img_object[param]();
719
                }
720
            case 'display_width':
721
            case 'display_height':
722
            case 'angle':
723
                return this.img_object[param]();
724
            case 'zoom':
725
                return this.current_zoom;
726
            case 'src':
727
                return this.img_object.object().attr('src');
728
            case 'coords':
729
                return {
730
                    x: this.img_object.x(),
731
                    y: this.img_object.y()
732
                };
733
        }
734
    },
735
 
736
    /**
737
    *   callback for handling mousdown event to start dragging image
738
    **/
739
    _mouseStart: function( e )
740
    {
741
        $.ui.mouse.prototype._mouseStart.call(this, e);
742
        if (this._trigger('onStartDrag', 0, this._getMouseCoords(e)) === false) {
743
            return false;
744
        }
745
 
746
        /* start drag event*/
747
        this.container.addClass("iviewer_drag_cursor");
748
 
749
        //#10: fix movement quirks for ipad
750
        this._dragInitialized = !(e.originalEvent.changedTouches && e.originalEvent.changedTouches.length==1);
751
 
752
        this.dx = e.pageX - this.img_object.x();
753
        this.dy = e.pageY - this.img_object.y();
754
        return true;
755
    },
756
 
757
    _mouseCapture: function( e ) {
758
        return true;
759
    },
760
 
761
    /**
762
     * Handle mouse move if needed. User can avoid using this callback, because
763
     *    he can get the same information through public methods.
764
     *  @param {jQuery.Event} e
765
     */
766
    _handleMouseMove: function(e) {
767
        this._trigger('onMouseMove', e, this._getMouseCoords(e));
768
    },
769
 
770
    /**
771
    *   callback for handling mousemove event to drag image
772
    **/
773
    _mouseDrag: function(e)
774
    {
775
        $.ui.mouse.prototype._mouseDrag.call(this, e);
776
 
777
        //#10: imitate mouseStart, because we can get here without it on iPad for some reason
778
        if (!this._dragInitialized) {
779
            this.dx = e.pageX - this.img_object.x();
780
            this.dy = e.pageY - this.img_object.y();
781
            this._dragInitialized = true;
782
        }
783
 
784
        var ltop =  e.pageY - this.dy;
785
        var lleft = e.pageX - this.dx;
786
 
787
        this.setCoords(lleft, ltop);
788
        this._trigger('onDrag', e, this._getMouseCoords(e));
789
        return false;
790
    },
791
 
792
    /**
793
    *   callback for handling stop drag
794
    **/
795
    _mouseStop: function(e)
796
    {
797
        $.ui.mouse.prototype._mouseStop.call(this, e);
798
        this.container.removeClass("iviewer_drag_cursor");
799
        this._trigger('onStopDrag', 0, this._getMouseCoords(e));
800
    },
801
 
802
    _click: function(e)
803
    {
804
        this._trigger('onClick', 0, this._getMouseCoords(e));
805
    },
806
 
807
    /**
808
    *   create zoom buttons info box
809
    **/
810
    createui: function()
811
    {
812
        var me=this;
813
 
814
        $("<div>", { 'class': "iviewer_zoom_in iviewer_common iviewer_button"})
815
                    .bind('mousedown touchstart',function(){me.zoom_by(1); return false;})
816
                    .appendTo(this.container);
817
 
818
        $("<div>", { 'class': "iviewer_zoom_out iviewer_common iviewer_button"})
819
                    .bind('mousedown touchstart',function(){me.zoom_by(- 1); return false;})
820
                    .appendTo(this.container);
821
 
822
        $("<div>", { 'class': "iviewer_zoom_zero iviewer_common iviewer_button"})
823
                    .bind('mousedown touchstart',function(){me.set_zoom(100); return false;})
824
                    .appendTo(this.container);
825
 
826
        $("<div>", { 'class': "iviewer_zoom_fit iviewer_common iviewer_button"})
827
                    .bind('mousedown touchstart',function(){me.fit(this); return false;})
828
                    .appendTo(this.container);
829
 
830
        this.zoom_object = $("<div>").addClass("iviewer_zoom_status iviewer_common")
831
                                    .appendTo(this.container);
832
 
833
        $("<div>", { 'class': "iviewer_rotate_left iviewer_common iviewer_button"})
834
                    .bind('mousedown touchstart',function(){me.angle(-90); return false;})
835
                    .appendTo(this.container);
836
 
837
        $("<div>", { 'class': "iviewer_rotate_right iviewer_common iviewer_button" })
838
                    .bind('mousedown touchstart',function(){me.angle(90); return false;})
839
                    .appendTo(this.container);
840
 
841
        this.update_status(); //initial status update
842
    }
843
 
844
} );
845
 
846
/**
847
 * @class $.ui.iviewer.ImageObject Class represents image and provides public api without
848
 *     extending image prototype.
849
 * @constructor
850
 * @param {boolean} do_anim Do we want to animate image on dimension changes?
851
 */
852
$.ui.iviewer.ImageObject = function(do_anim) {
853
    this._img = $("<img>")
854
            //this is needed, because chromium sets them auto otherwise
855
            .css({ position: "absolute", top :"0px", left: "0px"});
856
 
857
    this._loaded = false;
858
    this._swapDimensions = false;
859
    this._do_anim = do_anim || false;
860
    this.x(0, true);
861
    this.y(0, true);
862
    this.angle(0);
863
};
864
 
865
 
866
/** @lends $.ui.iviewer.ImageObject.prototype */
867
(function() {
868
    /**
869
     * Restore initial object state.
870
     *
871
     * @param {number} w Image width.
872
     * @param {number} h Image height.
873
     */
874
    this._reset = function(w, h) {
875
        this._angle = 0;
876
        this._swapDimensions = false;
877
        this.x(0);
878
        this.y(0);
879
 
880
        this.orig_width(w);
881
        this.orig_height(h);
882
        this.display_width(w);
883
        this.display_height(h);
884
    };
885
 
886
    /**
887
     * Check if image is loaded.
888
     *
889
     * @return {boolean}
890
     */
891
    this.loaded = function() { return this._loaded; };
892
 
893
    /**
894
     * Load image.
895
     *
896
     * @param {string} src Image url.
897
     * @param {Function=} loaded Function will be called on image load.
898
     */
899
    this.load = function(src, loaded, error) {
900
        var self = this;
901
 
902
        loaded = loaded || jQuery.noop;
903
        this._loaded = false;
904
 
905
        //If we assign new image url to the this._img IE9 fires onload event and image width and
906
        //height are set to zero. So, we create another image object and load image through it.
907
        var img = new Image();
908
        img.onload = function() {
909
            self._loaded = true;
910
            self._reset(this.width, this.height);
911
 
912
            self._img
913
                .removeAttr("width")
914
                .removeAttr("height")
915
                .removeAttr("style")
916
                //max-width is reset, because plugin breaks in the twitter bootstrap otherwise
917
                .css({ position: "absolute", top :"0px", left: "0px", maxWidth: "none"})
918
 
919
            self._img[0].src = src;
920
            loaded();
921
        };
922
 
923
        img.onerror = error;
924
 
925
        //we need this because sometimes internet explorer 8 fires onload event
926
        //right after assignment (synchronously)
927
        setTimeout(function() {
928
            img.src = src;
929
        }, 0);
930
 
931
        this.angle(0);
932
    };
933
 
934
    this._dimension = function(prefix, name) {
935
        var horiz = '_' + prefix + '_' + name,
936
            vert = '_' + prefix + '_' + (name === 'height' ? 'width' : 'height');
937
        return setter(function(val) {
938
                this[this._swapDimensions ? horiz: vert] = val;
939
            },
940
            function() {
941
                return this[this._swapDimensions ? horiz: vert];
942
            });
943
    };
944
 
945
    /**
946
     * Getters and setter for common image dimensions.
947
     *    display_ means real image tag dimensions
948
     *    orig_ means physical image dimensions.
949
     *  Note, that dimensions are swapped if image is rotated. It necessary,
950
     *  because as little as possible code should know about rotation.
951
     */
952
    this.display_width = this._dimension('display', 'width'),
953
    this.display_height = this._dimension('display', 'height'),
954
    this.display_diff = function() { return Math.floor( this.display_width() - this.display_height() ) };
955
    this.orig_width = this._dimension('orig', 'width'),
956
    this.orig_height = this._dimension('orig', 'height'),
957
 
958
    /**
959
     * Setter for  X coordinate. If image is rotated we need to additionaly shift an
960
     *     image to map image coordinate to the visual position.
961
     *
962
     * @param {number} val Coordinate value.
963
     * @param {boolean} skipCss If true, we only set the value and do not touch the dom.
964
     */
965
    this.x = setter(function(val, skipCss) {
966
            this._x = val;
967
            if (!skipCss) {
968
                this._finishAnimation();
969
                this._img.css("left",this._x + (this._swapDimensions ? this.display_diff() / 2 : 0) + "px");
970
            }
971
        },
972
        function() {
973
            return this._x;
974
        });
975
 
976
    /**
977
     * Setter for  Y coordinate. If image is rotated we need to additionaly shift an
978
     *     image to map image coordinate to the visual position.
979
     *
980
     * @param {number} val Coordinate value.
981
     * @param {boolean} skipCss If true, we only set the value and do not touch the dom.
982
     */
983
    this.y = setter(function(val, skipCss) {
984
            this._y = val;
985
            if (!skipCss) {
986
                this._finishAnimation();
987
                this._img.css("top",this._y - (this._swapDimensions ? this.display_diff() / 2 : 0) + "px");
988
            }
989
        },
990
       function() {
991
            return this._y;
992
       });
993
 
994
    /**
995
     * Perform image rotation.
996
     *
997
     * @param {number} deg Absolute image angle. The method will work with values 0, 90, 180, 270 degrees.
998
     */
999
    this.angle = setter(function(deg) {
1000
            var prevSwap = this._swapDimensions;
1001
 
1002
            this._angle = deg;
1003
            this._swapDimensions = deg % 180 !== 0;
1004
 
1005
            if (prevSwap !== this._swapDimensions) {
1006
                var verticalMod = this._swapDimensions ? -1 : 1;
1007
                this.x(this.x() - verticalMod * this.display_diff() / 2, true);
1008
                this.y(this.y() + verticalMod * this.display_diff() / 2, true);
1009
            };
1010
 
1011
            var cssVal = 'rotate(' + deg + 'deg)',
1012
                img = this._img;
1013
 
1014
            jQuery.each(['', '-webkit-', '-moz-', '-o-', '-ms-'], function(i, prefix) {
1015
                img.css(prefix + 'transform', cssVal);
1016
            });
1017
 
1018
            if (useIeTransforms) {
1019
                jQuery.each(['-ms-', ''], function(i, prefix) {
1020
                    img.css(prefix + 'filter', ieTransforms[deg].filter);
1021
                });
1022
 
1023
                img.css({
1024
                    marginLeft: ieTransforms[deg].marginLeft * this.display_diff() / 2,
1025
                    marginTop: ieTransforms[deg].marginTop * this.display_diff() / 2
1026
                });
1027
            }
1028
        },
1029
       function() { return this._angle; });
1030
 
1031
    /**
1032
     * Map point in the container coordinates to the point in image coordinates.
1033
     *     You will get coordinates of point on image with respect to rotation,
1034
     *     but will be set as if image was not rotated.
1035
     *     So, if image was rotated 90 degrees, it's (0,0) point will be on the
1036
     *     top right corner.
1037
     *
1038
     * @param {{x: number, y: number}} point Point in container coordinates.
1039
     * @return  {{x: number, y: number}}
1040
     */
1041
    this.toOriginalCoords = function(point) {
1042
        switch (this.angle()) {
1043
            case 0: return { x: point.x, y: point.y }
1044
            case 90: return { x: point.y, y: this.display_width() - point.x }
1045
            case 180: return { x: this.display_width() - point.x, y: this.display_height() - point.y }
1046
            case 270: return { x: this.display_height() - point.y, y: point.x }
1047
        }
1048
    };
1049
 
1050
    /**
1051
     * Map point in the image coordinates to the point in container coordinates.
1052
     *     You will get coordinates of point on container with respect to rotation.
1053
     *     Note, if image was rotated 90 degrees, it's (0,0) point will be on the
1054
     *     top right corner.
1055
     *
1056
     * @param {{x: number, y: number}} point Point in container coordinates.
1057
     * @return  {{x: number, y: number}}
1058
     */
1059
    this.toRealCoords = function(point) {
1060
        switch (this.angle()) {
1061
            case 0: return { x: this.x() + point.x, y: this.y() + point.y }
1062
            case 90: return { x: this.x() + this.display_width() - point.y, y: this.y() + point.x}
1063
            case 180: return { x: this.x() + this.display_width() - point.x, y: this.y() + this.display_height() - point.y}
1064
            case 270: return { x: this.x() + point.y, y: this.y() + this.display_height() - point.x}
1065
        }
1066
    };
1067
 
1068
    /**
1069
     * @return {jQuery} Return image node. this is needed to add event handlers.
1070
     */
1071
    this.object = setter(jQuery.noop,
1072
                           function() { return this._img; });
1073
 
1074
    /**
1075
     * Change image properties.
1076
     *
1077
     * @param {number} disp_w Display width;
1078
     * @param {number} disp_h Display height;
1079
     * @param {number} x
1080
     * @param {number} y
1081
     * @param {boolean} skip_animation If true, the animation will be skiped despite the
1082
     *     value set in constructor.
1083
     * @param {Function=} complete Call back will be fired when zoom will be complete.
1084
     */
1085
    this.setImageProps = function(disp_w, disp_h, x, y, skip_animation, complete) {
1086
        complete = complete || jQuery.noop;
1087
 
1088
        this.display_width(disp_w);
1089
        this.display_height(disp_h);
1090
        this.x(x, true);
1091
        this.y(y, true);
1092
 
1093
        var w = this._swapDimensions ? disp_h : disp_w;
1094
        var h = this._swapDimensions ? disp_w : disp_h;
1095
 
1096
        var params = {
1097
            width: w,
1098
            height: h,
1099
            top: y - (this._swapDimensions ? this.display_diff() / 2 : 0) + "px",
1100
            left: x + (this._swapDimensions ? this.display_diff() / 2 : 0) + "px"
1101
        };
1102
 
1103
        if (useIeTransforms) {
1104
            jQuery.extend(params, {
1105
                marginLeft: ieTransforms[this.angle()].marginLeft * this.display_diff() / 2,
1106
                marginTop: ieTransforms[this.angle()].marginTop * this.display_diff() / 2
1107
            });
1108
        }
1109
 
1110
        var swapDims = this._swapDimensions,
1111
            img = this._img;
1112
 
1113
        //here we come: another IE oddness. If image is rotated 90 degrees with a filter, than
1114
        //width and height getters return real width and height of rotated image. The bad news
1115
        //is that to set height you need to set a width and vice versa. Fuck IE.
1116
        //So, in this case we have to animate width and height manually.
1117
        if(useIeTransforms && swapDims) {
1118
            var ieh = this._img.width(),
1119
                iew = this._img.height(),
1120
                iedh = params.height - ieh;
1121
                iedw = params.width - iew;
1122
 
1123
            delete params.width;
1124
            delete params.height;
1125
        }
1126
 
1127
        if (this._do_anim && !skip_animation) {
1128
            this._img.stop(true)
1129
                .animate(params, {
1130
                    duration: 200,
1131
                    complete: complete,
1132
                    step: function(now, fx) {
1133
                        if(useIeTransforms && swapDims && (fx.prop === 'top')) {
1134
                            var percent = (now - fx.start) / (fx.end - fx.start);
1135
 
1136
                            img.height(ieh + iedh * percent);
1137
                            img.width(iew + iedw * percent);
1138
                            img.css('top', now);
1139
                        }
1140
                    }
1141
                });
1142
        } else {
1143
            this._img.css(params);
1144
            setTimeout(complete, 0); //both if branches should behave equally.
1145
        }
1146
    };
1147
 
1148
    //if we set image coordinates we need to be sure that no animation is active atm
1149
    this._finishAnimation = function() {
1150
      this._img.stop(true, true);
1151
    }
1152
 
1153
}).apply($.ui.iviewer.ImageObject.prototype);
1154
 
1155
 
1156
 
1157
var util = {
1158
    scaleValue: function(value, toZoom)
1159
    {
1160
        return value * toZoom / 100;
1161
    },
1162
 
1163
    descaleValue: function(value, fromZoom)
1164
    {
1165
        return value * 100 / fromZoom;
1166
    }
1167
};
1168
 
1169
 } )( jQuery, undefined );