~canonical-sysadmins/wordpress/4.7.2

« back to all changes in this revision

Viewing changes to wp-includes/js/imgareaselect/jquery.imgareaselect.js

  • Committer: Jacek Nykis
  • Date: 2015-01-05 16:17:05 UTC
  • Revision ID: jacek.nykis@canonical.com-20150105161705-w544l1h5mcg7u4w9
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * imgAreaSelect jQuery plugin
 
3
 * version 0.9.10
 
4
 *
 
5
 * Copyright (c) 2008-2013 Michal Wojciechowski (odyniec.net)
 
6
 *
 
7
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 
8
 * and GPL (GPL-LICENSE.txt) licenses.
 
9
 *
 
10
 * http://odyniec.net/projects/imgareaselect/
 
11
 *
 
12
 */
 
13
 
 
14
(function($) {
 
15
 
 
16
/*
 
17
 * Math functions will be used extensively, so it's convenient to make a few
 
18
 * shortcuts
 
19
 */
 
20
var abs = Math.abs,
 
21
    max = Math.max,
 
22
    min = Math.min,
 
23
    round = Math.round;
 
24
 
 
25
/**
 
26
 * Create a new HTML div element
 
27
 *
 
28
 * @return A jQuery object representing the new element
 
29
 */
 
30
function div() {
 
31
    return $('<div/>');
 
32
}
 
33
 
 
34
/**
 
35
 * imgAreaSelect initialization
 
36
 *
 
37
 * @param img
 
38
 *            A HTML image element to attach the plugin to
 
39
 * @param options
 
40
 *            An options object
 
41
 */
 
42
$.imgAreaSelect = function (img, options) {
 
43
    var
 
44
        /* jQuery object representing the image */
 
45
        $img = $(img),
 
46
 
 
47
        /* Has the image finished loading? */
 
48
        imgLoaded,
 
49
 
 
50
        /* Plugin elements */
 
51
 
 
52
        /* Container box */
 
53
        $box = div(),
 
54
        /* Selection area */
 
55
        $area = div(),
 
56
        /* Border (four divs) */
 
57
        $border = div().add(div()).add(div()).add(div()),
 
58
        /* Outer area (four divs) */
 
59
        $outer = div().add(div()).add(div()).add(div()),
 
60
        /* Handles (empty by default, initialized in setOptions()) */
 
61
        $handles = $([]),
 
62
 
 
63
        /*
 
64
         * Additional element to work around a cursor problem in Opera
 
65
         * (explained later)
 
66
         */
 
67
        $areaOpera,
 
68
 
 
69
        /* Image position (relative to viewport) */
 
70
        left, top,
 
71
 
 
72
        /* Image offset (as returned by .offset()) */
 
73
        imgOfs = { left: 0, top: 0 },
 
74
 
 
75
        /* Image dimensions (as returned by .width() and .height()) */
 
76
        imgWidth, imgHeight,
 
77
 
 
78
        /*
 
79
         * jQuery object representing the parent element that the plugin
 
80
         * elements are appended to
 
81
         */
 
82
        $parent,
 
83
 
 
84
        /* Parent element offset (as returned by .offset()) */
 
85
        parOfs = { left: 0, top: 0 },
 
86
 
 
87
        /* Base z-index for plugin elements */
 
88
        zIndex = 0,
 
89
 
 
90
        /* Plugin elements position */
 
91
        position = 'absolute',
 
92
 
 
93
        /* X/Y coordinates of the starting point for move/resize operations */
 
94
        startX, startY,
 
95
 
 
96
        /* Horizontal and vertical scaling factors */
 
97
        scaleX, scaleY,
 
98
 
 
99
        /* Current resize mode ("nw", "se", etc.) */
 
100
        resize,
 
101
 
 
102
        /* Selection area constraints */
 
103
        minWidth, minHeight, maxWidth, maxHeight,
 
104
 
 
105
        /* Aspect ratio to maintain (floating point number) */
 
106
        aspectRatio,
 
107
 
 
108
        /* Are the plugin elements currently displayed? */
 
109
        shown,
 
110
 
 
111
        /* Current selection (relative to parent element) */
 
112
        x1, y1, x2, y2,
 
113
 
 
114
        /* Current selection (relative to scaled image) */
 
115
        selection = { x1: 0, y1: 0, x2: 0, y2: 0, width: 0, height: 0 },
 
116
 
 
117
        /* Document element */
 
118
        docElem = document.documentElement,
 
119
 
 
120
        /* User agent */
 
121
        ua = navigator.userAgent,
 
122
 
 
123
        /* Various helper variables used throughout the code */
 
124
        $p, d, i, o, w, h, adjusted;
 
125
 
 
126
    /*
 
127
     * Translate selection coordinates (relative to scaled image) to viewport
 
128
     * coordinates (relative to parent element)
 
129
     */
 
130
 
 
131
    /**
 
132
     * Translate selection X to viewport X
 
133
     *
 
134
     * @param x
 
135
     *            Selection X
 
136
     * @return Viewport X
 
137
     */
 
138
    function viewX(x) {
 
139
        return x + imgOfs.left - parOfs.left;
 
140
    }
 
141
 
 
142
    /**
 
143
     * Translate selection Y to viewport Y
 
144
     *
 
145
     * @param y
 
146
     *            Selection Y
 
147
     * @return Viewport Y
 
148
     */
 
149
    function viewY(y) {
 
150
        return y + imgOfs.top - parOfs.top;
 
151
    }
 
152
 
 
153
    /*
 
154
     * Translate viewport coordinates to selection coordinates
 
155
     */
 
156
 
 
157
    /**
 
158
     * Translate viewport X to selection X
 
159
     *
 
160
     * @param x
 
161
     *            Viewport X
 
162
     * @return Selection X
 
163
     */
 
164
    function selX(x) {
 
165
        return x - imgOfs.left + parOfs.left;
 
166
    }
 
167
 
 
168
    /**
 
169
     * Translate viewport Y to selection Y
 
170
     *
 
171
     * @param y
 
172
     *            Viewport Y
 
173
     * @return Selection Y
 
174
     */
 
175
    function selY(y) {
 
176
        return y - imgOfs.top + parOfs.top;
 
177
    }
 
178
 
 
179
    /*
 
180
     * Translate event coordinates (relative to document) to viewport
 
181
     * coordinates
 
182
     */
 
183
 
 
184
    /**
 
185
     * Get event X and translate it to viewport X
 
186
     *
 
187
     * @param event
 
188
     *            The event object
 
189
     * @return Viewport X
 
190
     */
 
191
    function evX(event) {
 
192
        return event.pageX - parOfs.left;
 
193
    }
 
194
 
 
195
    /**
 
196
     * Get event Y and translate it to viewport Y
 
197
     *
 
198
     * @param event
 
199
     *            The event object
 
200
     * @return Viewport Y
 
201
     */
 
202
    function evY(event) {
 
203
        return event.pageY - parOfs.top;
 
204
    }
 
205
 
 
206
    /**
 
207
     * Get the current selection
 
208
     *
 
209
     * @param noScale
 
210
     *            If set to <code>true</code>, scaling is not applied to the
 
211
     *            returned selection
 
212
     * @return Selection object
 
213
     */
 
214
    function getSelection(noScale) {
 
215
        var sx = noScale || scaleX, sy = noScale || scaleY;
 
216
 
 
217
        return { x1: round(selection.x1 * sx),
 
218
            y1: round(selection.y1 * sy),
 
219
            x2: round(selection.x2 * sx),
 
220
            y2: round(selection.y2 * sy),
 
221
            width: round(selection.x2 * sx) - round(selection.x1 * sx),
 
222
            height: round(selection.y2 * sy) - round(selection.y1 * sy) };
 
223
    }
 
224
 
 
225
    /**
 
226
     * Set the current selection
 
227
     *
 
228
     * @param x1
 
229
     *            X coordinate of the upper left corner of the selection area
 
230
     * @param y1
 
231
     *            Y coordinate of the upper left corner of the selection area
 
232
     * @param x2
 
233
     *            X coordinate of the lower right corner of the selection area
 
234
     * @param y2
 
235
     *            Y coordinate of the lower right corner of the selection area
 
236
     * @param noScale
 
237
     *            If set to <code>true</code>, scaling is not applied to the
 
238
     *            new selection
 
239
     */
 
240
    function setSelection(x1, y1, x2, y2, noScale) {
 
241
        var sx = noScale || scaleX, sy = noScale || scaleY;
 
242
 
 
243
        selection = {
 
244
            x1: round(x1 / sx || 0),
 
245
            y1: round(y1 / sy || 0),
 
246
            x2: round(x2 / sx || 0),
 
247
            y2: round(y2 / sy || 0)
 
248
        };
 
249
 
 
250
        selection.width = selection.x2 - selection.x1;
 
251
        selection.height = selection.y2 - selection.y1;
 
252
    }
 
253
 
 
254
    /**
 
255
     * Recalculate image and parent offsets
 
256
     */
 
257
    function adjust() {
 
258
        /*
 
259
         * Do not adjust if image has not yet loaded or if width is not a
 
260
         * positive number. The latter might happen when imgAreaSelect is put
 
261
         * on a parent element which is then hidden.
 
262
         */
 
263
        if (!imgLoaded || !$img.width())
 
264
            return;
 
265
 
 
266
        /*
 
267
         * Get image offset. The .offset() method returns float values, so they
 
268
         * need to be rounded.
 
269
         */
 
270
        imgOfs = { left: round($img.offset().left), top: round($img.offset().top) };
 
271
 
 
272
        /* Get image dimensions */
 
273
        imgWidth = $img.innerWidth();
 
274
        imgHeight = $img.innerHeight();
 
275
 
 
276
        imgOfs.top += ($img.outerHeight() - imgHeight) >> 1;
 
277
        imgOfs.left += ($img.outerWidth() - imgWidth) >> 1;
 
278
 
 
279
        /* Set minimum and maximum selection area dimensions */
 
280
        minWidth = round(options.minWidth / scaleX) || 0;
 
281
        minHeight = round(options.minHeight / scaleY) || 0;
 
282
        maxWidth = round(min(options.maxWidth / scaleX || 1<<24, imgWidth));
 
283
        maxHeight = round(min(options.maxHeight / scaleY || 1<<24, imgHeight));
 
284
 
 
285
        /*
 
286
         * Workaround for jQuery 1.3.2 incorrect offset calculation, originally
 
287
         * observed in Safari 3. Firefox 2 is also affected.
 
288
         */
 
289
        if ($().jquery == '1.3.2' && position == 'fixed' &&
 
290
            !docElem['getBoundingClientRect'])
 
291
        {
 
292
            imgOfs.top += max(document.body.scrollTop, docElem.scrollTop);
 
293
            imgOfs.left += max(document.body.scrollLeft, docElem.scrollLeft);
 
294
        }
 
295
 
 
296
        /* Determine parent element offset */
 
297
        parOfs = /absolute|relative/.test($parent.css('position')) ?
 
298
            { left: round($parent.offset().left) - $parent.scrollLeft(),
 
299
                top: round($parent.offset().top) - $parent.scrollTop() } :
 
300
            position == 'fixed' ?
 
301
                { left: $(document).scrollLeft(), top: $(document).scrollTop() } :
 
302
                { left: 0, top: 0 };
 
303
 
 
304
        left = viewX(0);
 
305
        top = viewY(0);
 
306
 
 
307
        /*
 
308
         * Check if selection area is within image boundaries, adjust if
 
309
         * necessary
 
310
         */
 
311
        if (selection.x2 > imgWidth || selection.y2 > imgHeight)
 
312
            doResize();
 
313
    }
 
314
 
 
315
    /**
 
316
     * Update plugin elements
 
317
     *
 
318
     * @param resetKeyPress
 
319
     *            If set to <code>false</code>, this instance's keypress
 
320
     *            event handler is not activated
 
321
     */
 
322
    function update(resetKeyPress) {
 
323
        /* If plugin elements are hidden, do nothing */
 
324
        if (!shown) return;
 
325
 
 
326
        /*
 
327
         * Set the position and size of the container box and the selection area
 
328
         * inside it
 
329
         */
 
330
        $box.css({ left: viewX(selection.x1), top: viewY(selection.y1) })
 
331
            .add($area).width(w = selection.width).height(h = selection.height);
 
332
 
 
333
        /*
 
334
         * Reset the position of selection area, borders, and handles (IE6/IE7
 
335
         * position them incorrectly if we don't do this)
 
336
         */
 
337
        $area.add($border).add($handles).css({ left: 0, top: 0 });
 
338
 
 
339
        /* Set border dimensions */
 
340
        $border
 
341
            .width(max(w - $border.outerWidth() + $border.innerWidth(), 0))
 
342
            .height(max(h - $border.outerHeight() + $border.innerHeight(), 0));
 
343
 
 
344
        /* Arrange the outer area elements */
 
345
        $($outer[0]).css({ left: left, top: top,
 
346
            width: selection.x1, height: imgHeight });
 
347
        $($outer[1]).css({ left: left + selection.x1, top: top,
 
348
            width: w, height: selection.y1 });
 
349
        $($outer[2]).css({ left: left + selection.x2, top: top,
 
350
            width: imgWidth - selection.x2, height: imgHeight });
 
351
        $($outer[3]).css({ left: left + selection.x1, top: top + selection.y2,
 
352
            width: w, height: imgHeight - selection.y2 });
 
353
 
 
354
        w -= $handles.outerWidth();
 
355
        h -= $handles.outerHeight();
 
356
 
 
357
        /* Arrange handles */
 
358
        switch ($handles.length) {
 
359
        case 8:
 
360
            $($handles[4]).css({ left: w >> 1 });
 
361
            $($handles[5]).css({ left: w, top: h >> 1 });
 
362
            $($handles[6]).css({ left: w >> 1, top: h });
 
363
            $($handles[7]).css({ top: h >> 1 });
 
364
        case 4:
 
365
            $handles.slice(1,3).css({ left: w });
 
366
            $handles.slice(2,4).css({ top: h });
 
367
        }
 
368
 
 
369
        if (resetKeyPress !== false) {
 
370
            /*
 
371
             * Need to reset the document keypress event handler -- unbind the
 
372
             * current handler
 
373
             */
 
374
            if ($.imgAreaSelect.onKeyPress != docKeyPress)
 
375
                $(document).unbind($.imgAreaSelect.keyPress,
 
376
                    $.imgAreaSelect.onKeyPress);
 
377
 
 
378
            if (options.keys)
 
379
                /*
 
380
                 * Set the document keypress event handler to this instance's
 
381
                 * docKeyPress() function
 
382
                 */
 
383
                $(document)[$.imgAreaSelect.keyPress](
 
384
                    $.imgAreaSelect.onKeyPress = docKeyPress);
 
385
        }
 
386
 
 
387
        /*
 
388
         * Internet Explorer displays 1px-wide dashed borders incorrectly by
 
389
         * filling the spaces between dashes with white. Toggling the margin
 
390
         * property between 0 and "auto" fixes this in IE6 and IE7 (IE8 is still
 
391
         * broken). This workaround is not perfect, as it requires setTimeout()
 
392
         * and thus causes the border to flicker a bit, but I haven't found a
 
393
         * better solution.
 
394
         *
 
395
         * Note: This only happens with CSS borders, set with the borderWidth,
 
396
         * borderOpacity, borderColor1, and borderColor2 options (which are now
 
397
         * deprecated). Borders created with GIF background images are fine.
 
398
         */
 
399
        if (msie && $border.outerWidth() - $border.innerWidth() == 2) {
 
400
            $border.css('margin', 0);
 
401
            setTimeout(function () { $border.css('margin', 'auto'); }, 0);
 
402
        }
 
403
    }
 
404
 
 
405
    /**
 
406
     * Do the complete update sequence: recalculate offsets, update the
 
407
     * elements, and set the correct values of x1, y1, x2, and y2.
 
408
     *
 
409
     * @param resetKeyPress
 
410
     *            If set to <code>false</code>, this instance's keypress
 
411
     *            event handler is not activated
 
412
     */
 
413
    function doUpdate(resetKeyPress) {
 
414
        adjust();
 
415
        update(resetKeyPress);
 
416
        x1 = viewX(selection.x1); y1 = viewY(selection.y1);
 
417
        x2 = viewX(selection.x2); y2 = viewY(selection.y2);
 
418
    }
 
419
 
 
420
    /**
 
421
     * Hide or fade out an element (or multiple elements)
 
422
     *
 
423
     * @param $elem
 
424
     *            A jQuery object containing the element(s) to hide/fade out
 
425
     * @param fn
 
426
     *            Callback function to be called when fadeOut() completes
 
427
     */
 
428
    function hide($elem, fn) {
 
429
        options.fadeSpeed ? $elem.fadeOut(options.fadeSpeed, fn) : $elem.hide();
 
430
    }
 
431
 
 
432
    /**
 
433
     * Selection area mousemove event handler
 
434
     *
 
435
     * @param event
 
436
     *            The event object
 
437
     */
 
438
    function areaMouseMove(event) {
 
439
        var x = selX(evX(event)) - selection.x1,
 
440
            y = selY(evY(event)) - selection.y1;
 
441
 
 
442
        if (!adjusted) {
 
443
            adjust();
 
444
            adjusted = true;
 
445
 
 
446
            $box.one('mouseout', function () { adjusted = false; });
 
447
        }
 
448
 
 
449
        /* Clear the resize mode */
 
450
        resize = '';
 
451
 
 
452
        if (options.resizable) {
 
453
            /*
 
454
             * Check if the mouse pointer is over the resize margin area and set
 
455
             * the resize mode accordingly
 
456
             */
 
457
            if (y <= options.resizeMargin)
 
458
                resize = 'n';
 
459
            else if (y >= selection.height - options.resizeMargin)
 
460
                resize = 's';
 
461
            if (x <= options.resizeMargin)
 
462
                resize += 'w';
 
463
            else if (x >= selection.width - options.resizeMargin)
 
464
                resize += 'e';
 
465
        }
 
466
 
 
467
        $box.css('cursor', resize ? resize + '-resize' :
 
468
            options.movable ? 'move' : '');
 
469
        if ($areaOpera)
 
470
            $areaOpera.toggle();
 
471
    }
 
472
 
 
473
    /**
 
474
     * Document mouseup event handler
 
475
     *
 
476
     * @param event
 
477
     *            The event object
 
478
     */
 
479
    function docMouseUp(event) {
 
480
        /* Set back the default cursor */
 
481
        $('body').css('cursor', '');
 
482
        /*
 
483
         * If autoHide is enabled, or if the selection has zero width/height,
 
484
         * hide the selection and the outer area
 
485
         */
 
486
        if (options.autoHide || selection.width * selection.height == 0)
 
487
            hide($box.add($outer), function () { $(this).hide(); });
 
488
 
 
489
        $(document).unbind('mousemove', selectingMouseMove);
 
490
        $box.mousemove(areaMouseMove);
 
491
 
 
492
        options.onSelectEnd(img, getSelection());
 
493
    }
 
494
 
 
495
    /**
 
496
     * Selection area mousedown event handler
 
497
     *
 
498
     * @param event
 
499
     *            The event object
 
500
     * @return false
 
501
     */
 
502
    function areaMouseDown(event) {
 
503
        if (event.which != 1) return false;
 
504
 
 
505
        adjust();
 
506
 
 
507
        if (resize) {
 
508
            /* Resize mode is in effect */
 
509
            $('body').css('cursor', resize + '-resize');
 
510
 
 
511
            x1 = viewX(selection[/w/.test(resize) ? 'x2' : 'x1']);
 
512
            y1 = viewY(selection[/n/.test(resize) ? 'y2' : 'y1']);
 
513
 
 
514
            $(document).mousemove(selectingMouseMove)
 
515
                .one('mouseup', docMouseUp);
 
516
            $box.unbind('mousemove', areaMouseMove);
 
517
        }
 
518
        else if (options.movable) {
 
519
            startX = left + selection.x1 - evX(event);
 
520
            startY = top + selection.y1 - evY(event);
 
521
 
 
522
            $box.unbind('mousemove', areaMouseMove);
 
523
 
 
524
            $(document).mousemove(movingMouseMove)
 
525
                .one('mouseup', function () {
 
526
                    options.onSelectEnd(img, getSelection());
 
527
 
 
528
                    $(document).unbind('mousemove', movingMouseMove);
 
529
                    $box.mousemove(areaMouseMove);
 
530
                });
 
531
        }
 
532
        else
 
533
            $img.mousedown(event);
 
534
 
 
535
        return false;
 
536
    }
 
537
 
 
538
    /**
 
539
     * Adjust the x2/y2 coordinates to maintain aspect ratio (if defined)
 
540
     *
 
541
     * @param xFirst
 
542
     *            If set to <code>true</code>, calculate x2 first. Otherwise,
 
543
     *            calculate y2 first.
 
544
     */
 
545
    function fixAspectRatio(xFirst) {
 
546
        if (aspectRatio)
 
547
            if (xFirst) {
 
548
                x2 = max(left, min(left + imgWidth,
 
549
                    x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1)));
 
550
                y2 = round(max(top, min(top + imgHeight,
 
551
                    y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1))));
 
552
                x2 = round(x2);
 
553
            }
 
554
            else {
 
555
                y2 = max(top, min(top + imgHeight,
 
556
                    y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1)));
 
557
                x2 = round(max(left, min(left + imgWidth,
 
558
                    x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1))));
 
559
                y2 = round(y2);
 
560
            }
 
561
    }
 
562
 
 
563
    /**
 
564
     * Resize the selection area respecting the minimum/maximum dimensions and
 
565
     * aspect ratio
 
566
     */
 
567
    function doResize() {
 
568
        /*
 
569
         * Make sure the top left corner of the selection area stays within
 
570
         * image boundaries (it might not if the image source was dynamically
 
571
         * changed).
 
572
         */
 
573
        x1 = min(x1, left + imgWidth);
 
574
        y1 = min(y1, top + imgHeight);
 
575
 
 
576
        if (abs(x2 - x1) < minWidth) {
 
577
            /* Selection width is smaller than minWidth */
 
578
            x2 = x1 - minWidth * (x2 < x1 || -1);
 
579
 
 
580
            if (x2 < left)
 
581
                x1 = left + minWidth;
 
582
            else if (x2 > left + imgWidth)
 
583
                x1 = left + imgWidth - minWidth;
 
584
        }
 
585
 
 
586
        if (abs(y2 - y1) < minHeight) {
 
587
            /* Selection height is smaller than minHeight */
 
588
            y2 = y1 - minHeight * (y2 < y1 || -1);
 
589
 
 
590
            if (y2 < top)
 
591
                y1 = top + minHeight;
 
592
            else if (y2 > top + imgHeight)
 
593
                y1 = top + imgHeight - minHeight;
 
594
        }
 
595
 
 
596
        x2 = max(left, min(x2, left + imgWidth));
 
597
        y2 = max(top, min(y2, top + imgHeight));
 
598
 
 
599
        fixAspectRatio(abs(x2 - x1) < abs(y2 - y1) * aspectRatio);
 
600
 
 
601
        if (abs(x2 - x1) > maxWidth) {
 
602
            /* Selection width is greater than maxWidth */
 
603
            x2 = x1 - maxWidth * (x2 < x1 || -1);
 
604
            fixAspectRatio();
 
605
        }
 
606
 
 
607
        if (abs(y2 - y1) > maxHeight) {
 
608
            /* Selection height is greater than maxHeight */
 
609
            y2 = y1 - maxHeight * (y2 < y1 || -1);
 
610
            fixAspectRatio(true);
 
611
        }
 
612
 
 
613
        selection = { x1: selX(min(x1, x2)), x2: selX(max(x1, x2)),
 
614
            y1: selY(min(y1, y2)), y2: selY(max(y1, y2)),
 
615
            width: abs(x2 - x1), height: abs(y2 - y1) };
 
616
 
 
617
        update();
 
618
 
 
619
        options.onSelectChange(img, getSelection());
 
620
    }
 
621
 
 
622
    /**
 
623
     * Mousemove event handler triggered when the user is selecting an area
 
624
     *
 
625
     * @param event
 
626
     *            The event object
 
627
     * @return false
 
628
     */
 
629
    function selectingMouseMove(event) {
 
630
        x2 = /w|e|^$/.test(resize) || aspectRatio ? evX(event) : viewX(selection.x2);
 
631
        y2 = /n|s|^$/.test(resize) || aspectRatio ? evY(event) : viewY(selection.y2);
 
632
 
 
633
        doResize();
 
634
 
 
635
        return false;
 
636
    }
 
637
 
 
638
    /**
 
639
     * Move the selection area
 
640
     *
 
641
     * @param newX1
 
642
     *            New viewport X1
 
643
     * @param newY1
 
644
     *            New viewport Y1
 
645
     */
 
646
    function doMove(newX1, newY1) {
 
647
        x2 = (x1 = newX1) + selection.width;
 
648
        y2 = (y1 = newY1) + selection.height;
 
649
 
 
650
        $.extend(selection, { x1: selX(x1), y1: selY(y1), x2: selX(x2),
 
651
            y2: selY(y2) });
 
652
 
 
653
        update();
 
654
 
 
655
        options.onSelectChange(img, getSelection());
 
656
    }
 
657
 
 
658
    /**
 
659
     * Mousemove event handler triggered when the selection area is being moved
 
660
     *
 
661
     * @param event
 
662
     *            The event object
 
663
     * @return false
 
664
     */
 
665
    function movingMouseMove(event) {
 
666
        x1 = max(left, min(startX + evX(event), left + imgWidth - selection.width));
 
667
        y1 = max(top, min(startY + evY(event), top + imgHeight - selection.height));
 
668
 
 
669
        doMove(x1, y1);
 
670
 
 
671
        event.preventDefault();
 
672
        return false;
 
673
    }
 
674
 
 
675
    /**
 
676
     * Start selection
 
677
     */
 
678
    function startSelection() {
 
679
        $(document).unbind('mousemove', startSelection);
 
680
        adjust();
 
681
 
 
682
        x2 = x1;
 
683
        y2 = y1;
 
684
        doResize();
 
685
 
 
686
        resize = '';
 
687
 
 
688
        if (!$outer.is(':visible'))
 
689
            /* Show the plugin elements */
 
690
            $box.add($outer).hide().fadeIn(options.fadeSpeed||0);
 
691
 
 
692
        shown = true;
 
693
 
 
694
        $(document).unbind('mouseup', cancelSelection)
 
695
            .mousemove(selectingMouseMove).one('mouseup', docMouseUp);
 
696
        $box.unbind('mousemove', areaMouseMove);
 
697
 
 
698
        options.onSelectStart(img, getSelection());
 
699
    }
 
700
 
 
701
    /**
 
702
     * Cancel selection
 
703
     */
 
704
    function cancelSelection() {
 
705
        $(document).unbind('mousemove', startSelection)
 
706
            .unbind('mouseup', cancelSelection);
 
707
        hide($box.add($outer));
 
708
 
 
709
        setSelection(selX(x1), selY(y1), selX(x1), selY(y1));
 
710
 
 
711
        /* If this is an API call, callback functions should not be triggered */
 
712
        if (!(this instanceof $.imgAreaSelect)) {
 
713
            options.onSelectChange(img, getSelection());
 
714
            options.onSelectEnd(img, getSelection());
 
715
        }
 
716
    }
 
717
 
 
718
    /**
 
719
     * Image mousedown event handler
 
720
     *
 
721
     * @param event
 
722
     *            The event object
 
723
     * @return false
 
724
     */
 
725
    function imgMouseDown(event) {
 
726
        /* Ignore the event if animation is in progress */
 
727
        if (event.which != 1 || $outer.is(':animated')) return false;
 
728
 
 
729
        adjust();
 
730
        startX = x1 = evX(event);
 
731
        startY = y1 = evY(event);
 
732
 
 
733
        /* Selection will start when the mouse is moved */
 
734
        $(document).mousemove(startSelection).mouseup(cancelSelection);
 
735
 
 
736
        return false;
 
737
    }
 
738
 
 
739
    /**
 
740
     * Window resize event handler
 
741
     */
 
742
    function windowResize() {
 
743
        doUpdate(false);
 
744
    }
 
745
 
 
746
    /**
 
747
     * Image load event handler. This is the final part of the initialization
 
748
     * process.
 
749
     */
 
750
    function imgLoad() {
 
751
        imgLoaded = true;
 
752
 
 
753
        /* Set options */
 
754
        setOptions(options = $.extend({
 
755
            classPrefix: 'imgareaselect',
 
756
            movable: true,
 
757
            parent: 'body',
 
758
            resizable: true,
 
759
            resizeMargin: 10,
 
760
            onInit: function () {},
 
761
            onSelectStart: function () {},
 
762
            onSelectChange: function () {},
 
763
            onSelectEnd: function () {}
 
764
        }, options));
 
765
 
 
766
        $box.add($outer).css({ visibility: '' });
 
767
 
 
768
        if (options.show) {
 
769
            shown = true;
 
770
            adjust();
 
771
            update();
 
772
            $box.add($outer).hide().fadeIn(options.fadeSpeed||0);
 
773
        }
 
774
 
 
775
        /*
 
776
         * Call the onInit callback. The setTimeout() call is used to ensure
 
777
         * that the plugin has been fully initialized and the object instance is
 
778
         * available (so that it can be obtained in the callback).
 
779
         */
 
780
        setTimeout(function () { options.onInit(img, getSelection()); }, 0);
 
781
    }
 
782
 
 
783
    /**
 
784
     * Document keypress event handler
 
785
     *
 
786
     * @param event
 
787
     *            The event object
 
788
     * @return false
 
789
     */
 
790
    var docKeyPress = function(event) {
 
791
        var k = options.keys, d, t, key = event.keyCode;
 
792
 
 
793
        d = !isNaN(k.alt) && (event.altKey || event.originalEvent.altKey) ? k.alt :
 
794
            !isNaN(k.ctrl) && event.ctrlKey ? k.ctrl :
 
795
            !isNaN(k.shift) && event.shiftKey ? k.shift :
 
796
            !isNaN(k.arrows) ? k.arrows : 10;
 
797
 
 
798
        if (k.arrows == 'resize' || (k.shift == 'resize' && event.shiftKey) ||
 
799
            (k.ctrl == 'resize' && event.ctrlKey) ||
 
800
            (k.alt == 'resize' && (event.altKey || event.originalEvent.altKey)))
 
801
        {
 
802
            /* Resize selection */
 
803
 
 
804
            switch (key) {
 
805
            case 37:
 
806
                /* Left */
 
807
                d = -d;
 
808
            case 39:
 
809
                /* Right */
 
810
                t = max(x1, x2);
 
811
                x1 = min(x1, x2);
 
812
                x2 = max(t + d, x1);
 
813
                fixAspectRatio();
 
814
                break;
 
815
            case 38:
 
816
                /* Up */
 
817
                d = -d;
 
818
            case 40:
 
819
                /* Down */
 
820
                t = max(y1, y2);
 
821
                y1 = min(y1, y2);
 
822
                y2 = max(t + d, y1);
 
823
                fixAspectRatio(true);
 
824
                break;
 
825
            default:
 
826
                return;
 
827
            }
 
828
 
 
829
            doResize();
 
830
        }
 
831
        else {
 
832
            /* Move selection */
 
833
 
 
834
            x1 = min(x1, x2);
 
835
            y1 = min(y1, y2);
 
836
 
 
837
            switch (key) {
 
838
            case 37:
 
839
                /* Left */
 
840
                doMove(max(x1 - d, left), y1);
 
841
                break;
 
842
            case 38:
 
843
                /* Up */
 
844
                doMove(x1, max(y1 - d, top));
 
845
                break;
 
846
            case 39:
 
847
                /* Right */
 
848
                doMove(x1 + min(d, imgWidth - selX(x2)), y1);
 
849
                break;
 
850
            case 40:
 
851
                /* Down */
 
852
                doMove(x1, y1 + min(d, imgHeight - selY(y2)));
 
853
                break;
 
854
            default:
 
855
                return;
 
856
            }
 
857
        }
 
858
 
 
859
        return false;
 
860
    };
 
861
 
 
862
    /**
 
863
     * Apply style options to plugin element (or multiple elements)
 
864
     *
 
865
     * @param $elem
 
866
     *            A jQuery object representing the element(s) to style
 
867
     * @param props
 
868
     *            An object that maps option names to corresponding CSS
 
869
     *            properties
 
870
     */
 
871
    function styleOptions($elem, props) {
 
872
        for (var option in props)
 
873
            if (options[option] !== undefined)
 
874
                $elem.css(props[option], options[option]);
 
875
    }
 
876
 
 
877
    /**
 
878
     * Set plugin options
 
879
     *
 
880
     * @param newOptions
 
881
     *            The new options object
 
882
     */
 
883
    function setOptions(newOptions) {
 
884
        if (newOptions.parent)
 
885
            ($parent = $(newOptions.parent)).append($box.add($outer));
 
886
 
 
887
        /* Merge the new options with the existing ones */
 
888
        $.extend(options, newOptions);
 
889
 
 
890
        adjust();
 
891
 
 
892
        if (newOptions.handles != null) {
 
893
            /* Recreate selection area handles */
 
894
            $handles.remove();
 
895
            $handles = $([]);
 
896
 
 
897
            i = newOptions.handles ? newOptions.handles == 'corners' ? 4 : 8 : 0;
 
898
 
 
899
            while (i--)
 
900
                $handles = $handles.add(div());
 
901
 
 
902
            /* Add a class to handles and set the CSS properties */
 
903
            $handles.addClass(options.classPrefix + '-handle').css({
 
904
                position: 'absolute',
 
905
                /*
 
906
                 * The font-size property needs to be set to zero, otherwise
 
907
                 * Internet Explorer makes the handles too large
 
908
                 */
 
909
                fontSize: 0,
 
910
                zIndex: zIndex + 1 || 1
 
911
            });
 
912
 
 
913
            /*
 
914
             * If handle width/height has not been set with CSS rules, set the
 
915
             * default 5px
 
916
             */
 
917
            if (!parseInt($handles.css('width')) >= 0)
 
918
                $handles.width(5).height(5);
 
919
 
 
920
            /*
 
921
             * If the borderWidth option is in use, add a solid border to
 
922
             * handles
 
923
             */
 
924
            if (o = options.borderWidth)
 
925
                $handles.css({ borderWidth: o, borderStyle: 'solid' });
 
926
 
 
927
            /* Apply other style options */
 
928
            styleOptions($handles, { borderColor1: 'border-color',
 
929
                borderColor2: 'background-color',
 
930
                borderOpacity: 'opacity' });
 
931
        }
 
932
 
 
933
        /* Calculate scale factors */
 
934
        scaleX = options.imageWidth / imgWidth || 1;
 
935
        scaleY = options.imageHeight / imgHeight || 1;
 
936
 
 
937
        /* Set selection */
 
938
        if (newOptions.x1 != null) {
 
939
            setSelection(newOptions.x1, newOptions.y1, newOptions.x2,
 
940
                newOptions.y2);
 
941
            newOptions.show = !newOptions.hide;
 
942
        }
 
943
 
 
944
        if (newOptions.keys)
 
945
            /* Enable keyboard support */
 
946
            options.keys = $.extend({ shift: 1, ctrl: 'resize' },
 
947
                newOptions.keys);
 
948
 
 
949
        /* Add classes to plugin elements */
 
950
        $outer.addClass(options.classPrefix + '-outer');
 
951
        $area.addClass(options.classPrefix + '-selection');
 
952
        for (i = 0; i++ < 4;)
 
953
            $($border[i-1]).addClass(options.classPrefix + '-border' + i);
 
954
 
 
955
        /* Apply style options */
 
956
        styleOptions($area, { selectionColor: 'background-color',
 
957
            selectionOpacity: 'opacity' });
 
958
        styleOptions($border, { borderOpacity: 'opacity',
 
959
            borderWidth: 'border-width' });
 
960
        styleOptions($outer, { outerColor: 'background-color',
 
961
            outerOpacity: 'opacity' });
 
962
        if (o = options.borderColor1)
 
963
            $($border[0]).css({ borderStyle: 'solid', borderColor: o });
 
964
        if (o = options.borderColor2)
 
965
            $($border[1]).css({ borderStyle: 'dashed', borderColor: o });
 
966
 
 
967
        /* Append all the selection area elements to the container box */
 
968
        $box.append($area.add($border).add($areaOpera)).append($handles);
 
969
 
 
970
        if (msie) {
 
971
            if (o = ($outer.css('filter')||'').match(/opacity=(\d+)/))
 
972
                $outer.css('opacity', o[1]/100);
 
973
            if (o = ($border.css('filter')||'').match(/opacity=(\d+)/))
 
974
                $border.css('opacity', o[1]/100);
 
975
        }
 
976
 
 
977
        if (newOptions.hide)
 
978
            hide($box.add($outer));
 
979
        else if (newOptions.show && imgLoaded) {
 
980
            shown = true;
 
981
            $box.add($outer).fadeIn(options.fadeSpeed||0);
 
982
            doUpdate();
 
983
        }
 
984
 
 
985
        /* Calculate the aspect ratio factor */
 
986
        aspectRatio = (d = (options.aspectRatio || '').split(/:/))[0] / d[1];
 
987
 
 
988
        $img.add($outer).unbind('mousedown', imgMouseDown);
 
989
 
 
990
        if (options.disable || options.enable === false) {
 
991
            /* Disable the plugin */
 
992
            $box.unbind('mousemove', areaMouseMove).unbind('mousedown', areaMouseDown);
 
993
            $(window).unbind('resize', windowResize);
 
994
        }
 
995
        else {
 
996
            if (options.enable || options.disable === false) {
 
997
                /* Enable the plugin */
 
998
                if (options.resizable || options.movable)
 
999
                    $box.mousemove(areaMouseMove).mousedown(areaMouseDown);
 
1000
 
 
1001
                $(window).resize(windowResize);
 
1002
            }
 
1003
 
 
1004
            if (!options.persistent)
 
1005
                $img.add($outer).mousedown(imgMouseDown);
 
1006
        }
 
1007
 
 
1008
        options.enable = options.disable = undefined;
 
1009
    }
 
1010
 
 
1011
    /**
 
1012
     * Remove plugin completely
 
1013
     */
 
1014
    this.remove = function () {
 
1015
        /*
 
1016
         * Call setOptions with { disable: true } to unbind the event handlers
 
1017
         */
 
1018
        setOptions({ disable: true });
 
1019
        $box.add($outer).remove();
 
1020
    };
 
1021
 
 
1022
    /*
 
1023
     * Public API
 
1024
     */
 
1025
 
 
1026
    /**
 
1027
     * Get current options
 
1028
     *
 
1029
     * @return An object containing the set of options currently in use
 
1030
     */
 
1031
    this.getOptions = function () { return options; };
 
1032
 
 
1033
    /**
 
1034
     * Set plugin options
 
1035
     *
 
1036
     * @param newOptions
 
1037
     *            The new options object
 
1038
     */
 
1039
    this.setOptions = setOptions;
 
1040
 
 
1041
    /**
 
1042
     * Get the current selection
 
1043
     *
 
1044
     * @param noScale
 
1045
     *            If set to <code>true</code>, scaling is not applied to the
 
1046
     *            returned selection
 
1047
     * @return Selection object
 
1048
     */
 
1049
    this.getSelection = getSelection;
 
1050
 
 
1051
    /**
 
1052
     * Set the current selection
 
1053
     *
 
1054
     * @param x1
 
1055
     *            X coordinate of the upper left corner of the selection area
 
1056
     * @param y1
 
1057
     *            Y coordinate of the upper left corner of the selection area
 
1058
     * @param x2
 
1059
     *            X coordinate of the lower right corner of the selection area
 
1060
     * @param y2
 
1061
     *            Y coordinate of the lower right corner of the selection area
 
1062
     * @param noScale
 
1063
     *            If set to <code>true</code>, scaling is not applied to the
 
1064
     *            new selection
 
1065
     */
 
1066
    this.setSelection = setSelection;
 
1067
 
 
1068
    /**
 
1069
     * Cancel selection
 
1070
     */
 
1071
    this.cancelSelection = cancelSelection;
 
1072
 
 
1073
    /**
 
1074
     * Update plugin elements
 
1075
     *
 
1076
     * @param resetKeyPress
 
1077
     *            If set to <code>false</code>, this instance's keypress
 
1078
     *            event handler is not activated
 
1079
     */
 
1080
    this.update = doUpdate;
 
1081
 
 
1082
    /* Do the dreaded browser detection */
 
1083
    var msie = (/msie ([\w.]+)/i.exec(ua)||[])[1],
 
1084
        opera = /opera/i.test(ua),
 
1085
        safari = /webkit/i.test(ua) && !/chrome/i.test(ua);
 
1086
 
 
1087
    /*
 
1088
     * Traverse the image's parent elements (up to <body>) and find the
 
1089
     * highest z-index
 
1090
     */
 
1091
    $p = $img;
 
1092
 
 
1093
    while ($p.length) {
 
1094
        zIndex = max(zIndex,
 
1095
            !isNaN($p.css('z-index')) ? $p.css('z-index') : zIndex);
 
1096
        /* Also check if any of the ancestor elements has fixed position */
 
1097
        if ($p.css('position') == 'fixed')
 
1098
            position = 'fixed';
 
1099
 
 
1100
        $p = $p.parent(':not(body)');
 
1101
    }
 
1102
 
 
1103
    /*
 
1104
     * If z-index is given as an option, it overrides the one found by the
 
1105
     * above loop
 
1106
     */
 
1107
    zIndex = options.zIndex || zIndex;
 
1108
 
 
1109
    if (msie)
 
1110
        $img.attr('unselectable', 'on');
 
1111
 
 
1112
    /*
 
1113
     * In MSIE and WebKit, we need to use the keydown event instead of keypress
 
1114
     */
 
1115
    $.imgAreaSelect.keyPress = msie || safari ? 'keydown' : 'keypress';
 
1116
 
 
1117
    /*
 
1118
     * There is a bug affecting the CSS cursor property in Opera (observed in
 
1119
     * versions up to 10.00) that prevents the cursor from being updated unless
 
1120
     * the mouse leaves and enters the element again. To trigger the mouseover
 
1121
     * event, we're adding an additional div to $box and we're going to toggle
 
1122
     * it when mouse moves inside the selection area.
 
1123
     */
 
1124
    if (opera)
 
1125
        $areaOpera = div().css({ width: '100%', height: '100%',
 
1126
            position: 'absolute', zIndex: zIndex + 2 || 2 });
 
1127
 
 
1128
    /*
 
1129
     * We initially set visibility to "hidden" as a workaround for a weird
 
1130
     * behaviour observed in Google Chrome 1.0.154.53 (on Windows XP). Normally
 
1131
     * we would just set display to "none", but, for some reason, if we do so
 
1132
     * then Chrome refuses to later display the element with .show() or
 
1133
     * .fadeIn().
 
1134
     */
 
1135
    $box.add($outer).css({ visibility: 'hidden', position: position,
 
1136
        overflow: 'hidden', zIndex: zIndex || '0' });
 
1137
    $box.css({ zIndex: zIndex + 2 || 2 });
 
1138
    $area.add($border).css({ position: 'absolute', fontSize: 0 });
 
1139
 
 
1140
    /*
 
1141
     * If the image has been fully loaded, or if it is not really an image (eg.
 
1142
     * a div), call imgLoad() immediately; otherwise, bind it to be called once
 
1143
     * on image load event.
 
1144
     */
 
1145
    img.complete || img.readyState == 'complete' || !$img.is('img') ?
 
1146
        imgLoad() : $img.one('load', imgLoad);
 
1147
 
 
1148
    /*
 
1149
     * MSIE 9.0 doesn't always fire the image load event -- resetting the src
 
1150
     * attribute seems to trigger it. The check is for version 7 and above to
 
1151
     * accommodate for MSIE 9 running in compatibility mode.
 
1152
     */
 
1153
    if (!imgLoaded && msie && msie >= 7)
 
1154
        img.src = img.src;
 
1155
};
 
1156
 
 
1157
/**
 
1158
 * Invoke imgAreaSelect on a jQuery object containing the image(s)
 
1159
 *
 
1160
 * @param options
 
1161
 *            Options object
 
1162
 * @return The jQuery object or a reference to imgAreaSelect instance (if the
 
1163
 *         <code>instance</code> option was specified)
 
1164
 */
 
1165
$.fn.imgAreaSelect = function (options) {
 
1166
    options = options || {};
 
1167
 
 
1168
    this.each(function () {
 
1169
        /* Is there already an imgAreaSelect instance bound to this element? */
 
1170
        if ($(this).data('imgAreaSelect')) {
 
1171
            /* Yes there is -- is it supposed to be removed? */
 
1172
            if (options.remove) {
 
1173
                /* Remove the plugin */
 
1174
                $(this).data('imgAreaSelect').remove();
 
1175
                $(this).removeData('imgAreaSelect');
 
1176
            }
 
1177
            else
 
1178
                /* Reset options */
 
1179
                $(this).data('imgAreaSelect').setOptions(options);
 
1180
        }
 
1181
        else if (!options.remove) {
 
1182
            /* No exising instance -- create a new one */
 
1183
 
 
1184
            /*
 
1185
             * If neither the "enable" nor the "disable" option is present, add
 
1186
             * "enable" as the default
 
1187
             */
 
1188
            if (options.enable === undefined && options.disable === undefined)
 
1189
                options.enable = true;
 
1190
 
 
1191
            $(this).data('imgAreaSelect', new $.imgAreaSelect(this, options));
 
1192
        }
 
1193
    });
 
1194
 
 
1195
    if (options.instance)
 
1196
        /*
 
1197
         * Return the imgAreaSelect instance bound to the first element in the
 
1198
         * set
 
1199
         */
 
1200
        return $(this).data('imgAreaSelect');
 
1201
 
 
1202
    return this;
 
1203
};
 
1204
 
 
1205
})(jQuery);