WordPress.org

Make WordPress Core

Ticket #17446: 17446.diff

File 17446.diff, 40.4 KB (added by duck_, 7 years ago)
  • wp-includes/js/imgareaselect/jquery.imgareaselect.dev.js

     
    11/*
    22 * imgAreaSelect jQuery plugin
    3  * version 0.9.1
     3 * version 0.9.6
    44 *
    5  * Copyright (c) 2008-2009 Michal Wojciechowski (odyniec.net)
     5 * Copyright (c) 2008-2011 Michal Wojciechowski (odyniec.net)
    66 *
    77 * Dual licensed under the MIT (MIT-LICENSE.txt)
    88 * and GPL (GPL-LICENSE.txt) licenses.
     
    1313
    1414(function($) {
    1515
     16/*
     17 * Math functions will be used extensively, so it's convenient to make a few
     18 * shortcuts
     19 */   
    1620var abs = Math.abs,
    1721    max = Math.max,
    1822    min = Math.min,
    1923    round = Math.round;
    2024
     25/**
     26 * Create a new HTML div element
     27 *
     28 * @return A jQuery object representing the new element
     29 */
    2130function div() {
    2231    return $('<div/>');
    2332}
    2433
     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 */
    2542$.imgAreaSelect = function (img, options) {
    26     var
    27 
     43    var 
     44        /* jQuery object representing the image */
    2845        $img = $(img),
    29 
     46       
     47        /* Has the image finished loading? */
    3048        imgLoaded,
    31 
     49       
     50        /* Plugin elements */
     51       
     52        /* Container box */
    3253        $box = div(),
     54        /* Selection area */
    3355        $area = div(),
     56        /* Border (four divs) */
    3457        $border = div().add(div()).add(div()).add(div()),
     58        /* Outer area (four divs) */
    3559        $outer = div().add(div()).add(div()).add(div()),
     60        /* Handles (empty by default, initialized in setOptions()) */
    3661        $handles = $([]),
    37 
     62       
     63        /*
     64         * Additional element to work around a cursor problem in Opera
     65         * (explained later)
     66         */
    3867        $areaOpera,
    39 
     68       
     69        /* Image position (relative to viewport) */
    4070        left, top,
    41 
    42         imgOfs,
    43 
     71       
     72        /* Image offset (as returned by .offset()) */
     73        imgOfs = { left: 0, top: 0 },
     74       
     75        /* Image dimensions (as returned by .width() and .height()) */
    4476        imgWidth, imgHeight,
    45 
     77       
     78        /*
     79         * jQuery object representing the parent element that the plugin
     80         * elements are appended to
     81         */
    4682        $parent,
    47 
    48         parOfs,
    49 
     83       
     84        /* Parent element offset (as returned by .offset()) */
     85        parOfs = { left: 0, top: 0 },
     86       
     87        /* Base z-index for plugin elements */
    5088        zIndex = 0,
    51 
     89               
     90        /* Plugin elements position */
    5291        position = 'absolute',
    53 
     92       
     93        /* X/Y coordinates of the starting point for move/resize operations */
    5494        startX, startY,
    55 
     95       
     96        /* Horizontal and vertical scaling factors */
    5697        scaleX, scaleY,
    57 
     98       
     99        /*
     100         * Width of the margin inside the selection area where resize mode is
     101         * triggered
     102         */
    58103        resizeMargin = 10,
    59 
     104       
     105        /* Current resize mode ("nw", "se", etc.) */
    60106        resize,
    61 
     107       
     108        /* Selection area constraints */
     109        minWidth, minHeight, maxWidth, maxHeight,
     110       
     111        /* Aspect ratio to maintain (floating point number) */
    62112        aspectRatio,
    63 
     113       
     114        /* Are the plugin elements currently displayed? */
    64115        shown,
    65 
     116       
     117        /* Current selection (relative to parent element) */
    66118        x1, y1, x2, y2,
    67 
     119       
     120        /* Current selection (relative to scaled image) */
    68121        selection = { x1: 0, y1: 0, x2: 0, y2: 0, width: 0, height: 0 },
    69 
     122       
     123        /* Document element */
     124        docElem = document.documentElement,
     125       
     126        /* Various helper variables used throughout the code */
    70127        $p, d, i, o, w, h, adjusted;
    71128
     129    /*
     130     * Translate selection coordinates (relative to scaled image) to viewport
     131     * coordinates (relative to parent element)
     132     */
     133   
     134    /**
     135     * Translate selection X to viewport X
     136     *
     137     * @param x
     138     *            Selection X
     139     * @return Viewport X
     140     */
    72141    function viewX(x) {
    73142        return x + imgOfs.left - parOfs.left;
    74143    }
    75144
     145    /**
     146     * Translate selection Y to viewport Y
     147     *
     148     * @param y
     149     *            Selection Y
     150     * @return Viewport Y
     151     */
    76152    function viewY(y) {
    77153        return y + imgOfs.top - parOfs.top;
    78154    }
    79155
     156    /*
     157     * Translate viewport coordinates to selection coordinates
     158     */
     159   
     160    /**
     161     * Translate viewport X to selection X
     162     *
     163     * @param x
     164     *            Viewport X
     165     * @return Selection X
     166     */
    80167    function selX(x) {
    81168        return x - imgOfs.left + parOfs.left;
    82169    }
    83170
     171    /**
     172     * Translate viewport Y to selection Y
     173     *
     174     * @param y
     175     *            Viewport Y
     176     * @return Selection Y
     177     */
    84178    function selY(y) {
    85179        return y - imgOfs.top + parOfs.top;
    86180    }
    87 
     181   
     182    /*
     183     * Translate event coordinates (relative to document) to viewport
     184     * coordinates
     185     */
     186   
     187    /**
     188     * Get event X and translate it to viewport X
     189     *
     190     * @param event
     191     *            The event object
     192     * @return Viewport X
     193     */
    88194    function evX(event) {
    89195        return event.pageX - parOfs.left;
    90196    }
    91197
     198    /**
     199     * Get event Y and translate it to viewport Y
     200     *
     201     * @param event
     202     *            The event object
     203     * @return Viewport Y
     204     */
    92205    function evY(event) {
    93206        return event.pageY - parOfs.top;
    94207    }
    95208
     209    /**
     210     * Get the current selection
     211     *
     212     * @param noScale
     213     *            If set to <code>true</code>, scaling is not applied to the
     214     *            returned selection
     215     * @return Selection object
     216     */
    96217    function getSelection(noScale) {
    97218        var sx = noScale || scaleX, sy = noScale || scaleY;
    98 
     219       
    99220        return { x1: round(selection.x1 * sx),
    100221            y1: round(selection.y1 * sy),
    101222            x2: round(selection.x2 * sx),
     
    103224            width: round(selection.x2 * sx) - round(selection.x1 * sx),
    104225            height: round(selection.y2 * sy) - round(selection.y1 * sy) };
    105226    }
    106 
     227   
     228    /**
     229     * Set the current selection
     230     *
     231     * @param x1
     232     *            X coordinate of the upper left corner of the selection area
     233     * @param y1
     234     *            Y coordinate of the upper left corner of the selection area
     235     * @param x2
     236     *            X coordinate of the lower right corner of the selection area
     237     * @param y2
     238     *            Y coordinate of the lower right corner of the selection area
     239     * @param noScale
     240     *            If set to <code>true</code>, scaling is not applied to the
     241     *            new selection
     242     */
    107243    function setSelection(x1, y1, x2, y2, noScale) {
    108244        var sx = noScale || scaleX, sy = noScale || scaleY;
    109 
     245       
    110246        selection = {
    111             x1: round(x1 / sx),
    112             y1: round(y1 / sy),
    113             x2: round(x2 / sx),
    114             y2: round(y2 / sy)
     247            x1: round(x1 / sx || 0),
     248            y1: round(y1 / sy || 0),
     249            x2: round(x2 / sx || 0),
     250            y2: round(y2 / sy || 0)
    115251        };
    116 
    117         selection.width = (x2 = viewX(selection.x2)) - (x1 = viewX(selection.x1));
    118         selection.height = (y2 = viewX(selection.y2)) - (y1 = viewX(selection.y1));
     252       
     253        selection.width = selection.x2 - selection.x1;
     254        selection.height = selection.y2 - selection.y1;
    119255    }
    120256
     257    /**
     258     * Recalculate image and parent offsets
     259     */
    121260    function adjust() {
     261        /*
     262         * Do not adjust if image width is not a positive number. This might
     263         * happen when imgAreaSelect is put on a parent element which is then
     264         * hidden.
     265         */
    122266        if (!$img.width())
    123267            return;
    124 
     268       
     269        /*
     270         * Get image offset. The .offset() method returns float values, so they
     271         * need to be rounded.
     272         */
    125273        imgOfs = { left: round($img.offset().left), top: round($img.offset().top) };
     274       
     275        /* Get image dimensions */
     276        imgWidth = $img.innerWidth();
     277        imgHeight = $img.innerHeight();
     278       
     279        imgOfs.top += ($img.outerHeight() - imgHeight) >> 1;
     280        imgOfs.left += ($img.outerWidth() - imgWidth) >> 1;
    126281
    127         imgWidth = $img.width();
    128         imgHeight = $img.height();
    129 
    130         if ($().jquery == '1.3.2' && $.browser.safari && position == 'fixed') {
    131             imgOfs.top += max(document.documentElement.scrollTop, $('body').scrollTop());
    132 
    133             imgOfs.left += max(document.documentElement.scrollLeft, $('body').scrollLeft());
     282        /* Set minimum and maximum selection area dimensions */
     283        minWidth = options.minWidth || 0;
     284        minHeight = options.minHeight || 0;
     285        maxWidth = min(options.maxWidth || 1<<24, imgWidth);
     286        maxHeight = min(options.maxHeight || 1<<24, imgHeight);
     287       
     288        /*
     289         * Workaround for jQuery 1.3.2 incorrect offset calculation, originally
     290         * observed in Safari 3. Firefox 2 is also affected.
     291         */
     292        if ($().jquery == '1.3.2' && position == 'fixed' &&
     293            !docElem['getBoundingClientRect'])
     294        {
     295            imgOfs.top += max(document.body.scrollTop, docElem.scrollTop);
     296            imgOfs.left += max(document.body.scrollLeft, docElem.scrollLeft);
    134297        }
    135298
     299        /* Determine parent element offset */
    136300        parOfs = $.inArray($parent.css('position'), ['absolute', 'relative']) + 1 ?
    137301            { left: round($parent.offset().left) - $parent.scrollLeft(),
    138302                top: round($parent.offset().top) - $parent.scrollTop() } :
    139303            position == 'fixed' ?
    140304                { left: $(document).scrollLeft(), top: $(document).scrollTop() } :
    141305                { left: 0, top: 0 };
    142 
     306               
    143307        left = viewX(0);
    144308        top = viewY(0);
     309       
     310        /*
     311         * Check if selection area is within image boundaries, adjust if
     312         * necessary
     313         */
     314        if (selection.x2 > imgWidth || selection.y2 > imgHeight)
     315            doResize();
    145316    }
    146317
     318    /**
     319     * Update plugin elements
     320     *
     321     * @param resetKeyPress
     322     *            If set to <code>false</code>, this instance's keypress
     323     *            event handler is not activated
     324     */
    147325    function update(resetKeyPress) {
     326        /* If plugin elements are hidden, do nothing */
    148327        if (!shown) return;
    149328
     329        /*
     330         * Set the position and size of the container box and the selection area
     331         * inside it
     332         */
    150333        $box.css({ left: viewX(selection.x1), top: viewY(selection.y1) })
    151334            .add($area).width(w = selection.width).height(h = selection.height);
    152335
     336        /*
     337         * Reset the position of selection area, borders, and handles (IE6/IE7
     338         * position them incorrectly if we don't do this)
     339         */
    153340        $area.add($border).add($handles).css({ left: 0, top: 0 });
    154341
     342        /* Set border dimensions */
    155343        $border
    156344            .width(max(w - $border.outerWidth() + $border.innerWidth(), 0))
    157345            .height(max(h - $border.outerHeight() + $border.innerHeight(), 0));
    158346
     347        /* Arrange the outer area elements */
    159348        $($outer[0]).css({ left: left, top: top,
    160349            width: selection.x1, height: imgHeight });
    161350        $($outer[1]).css({ left: left + selection.x1, top: top,
     
    164353            width: imgWidth - selection.x2, height: imgHeight });
    165354        $($outer[3]).css({ left: left + selection.x1, top: top + selection.y2,
    166355            width: w, height: imgHeight - selection.y2 });
    167 
     356       
    168357        w -= $handles.outerWidth();
    169358        h -= $handles.outerHeight();
    170 
     359       
     360        /* Arrange handles */
    171361        switch ($handles.length) {
    172362        case 8:
    173             $($handles[4]).css({ left: w / 2 });
    174             $($handles[5]).css({ left: w, top: h / 2 });
    175             $($handles[6]).css({ left: w / 2, top: h });
    176             $($handles[7]).css({ top: h / 2 });
     363            $($handles[4]).css({ left: w >> 1 });
     364            $($handles[5]).css({ left: w, top: h >> 1 });
     365            $($handles[6]).css({ left: w >> 1, top: h });
     366            $($handles[7]).css({ top: h >> 1 });
    177367        case 4:
    178368            $handles.slice(1,3).css({ left: w });
    179369            $handles.slice(2,4).css({ top: h });
    180370        }
    181371
    182372        if (resetKeyPress !== false) {
     373            /*
     374             * Need to reset the document keypress event handler -- unbind the
     375             * current handler
     376             */
    183377            if ($.imgAreaSelect.keyPress != docKeyPress)
    184378                $(document).unbind($.imgAreaSelect.keyPress,
    185379                    $.imgAreaSelect.onKeyPress);
    186380
    187381            if (options.keys)
     382                /*
     383                 * Set the document keypress event handler to this instance's
     384                 * docKeyPress() function
     385                 */
    188386                $(document)[$.imgAreaSelect.keyPress](
    189387                    $.imgAreaSelect.onKeyPress = docKeyPress);
    190388        }
    191389
     390        /*
     391         * Internet Explorer displays 1px-wide dashed borders incorrectly by
     392         * filling the spaces between dashes with white. Toggling the margin
     393         * property between 0 and "auto" fixes this in IE6 and IE7 (IE8 is still
     394         * broken). This workaround is not perfect, as it requires setTimeout()
     395         * and thus causes the border to flicker a bit, but I haven't found a
     396         * better solution.
     397         *
     398         * Note: This only happens with CSS borders, set with the borderWidth,
     399         * borderOpacity, borderColor1, and borderColor2 options (which are now
     400         * deprecated). Borders created with GIF background images are fine.
     401         */
    192402        if ($.browser.msie && $border.outerWidth() - $border.innerWidth() == 2) {
    193403            $border.css('margin', 0);
    194404            setTimeout(function () { $border.css('margin', 'auto'); }, 0);
    195405        }
    196406    }
    197 
     407   
     408    /**
     409     * Do the complete update sequence: recalculate offsets, update the
     410     * elements, and set the correct values of x1, y1, x2, and y2.
     411     *
     412     * @param resetKeyPress
     413     *            If set to <code>false</code>, this instance's keypress
     414     *            event handler is not activated
     415     */
    198416    function doUpdate(resetKeyPress) {
    199417        adjust();
    200418        update(resetKeyPress);
    201419        x1 = viewX(selection.x1); y1 = viewY(selection.y1);
    202420        x2 = viewX(selection.x2); y2 = viewY(selection.y2);
    203421    }
    204 
     422   
     423    /**
     424     * Hide or fade out an element (or multiple elements)
     425     *
     426     * @param $elem
     427     *            A jQuery object containing the element(s) to hide/fade out
     428     * @param fn
     429     *            Callback function to be called when fadeOut() completes
     430     */
    205431    function hide($elem, fn) {
    206         options.fadeSpeed ? $elem.fadeOut(options.fadeSpeed, fn) : $elem.hide();
    207 
     432        options.fadeSpeed ? $elem.fadeOut(options.fadeSpeed, fn) : $elem.hide();
    208433    }
    209434
     435    /**
     436     * Selection area mousemove event handler
     437     *
     438     * @param event
     439     *            The event object
     440     */
    210441    function areaMouseMove(event) {
    211442        var x = selX(evX(event)) - selection.x1,
    212443            y = selY(evY(event)) - selection.y1;
    213 
     444       
    214445        if (!adjusted) {
    215446            adjust();
    216447            adjusted = true;
     
    218449            $box.one('mouseout', function () { adjusted = false; });
    219450        }
    220451
     452        /* Clear the resize mode */
    221453        resize = '';
    222454
    223455        if (options.resizable) {
    224             if (y <= resizeMargin)
     456            /*
     457             * Check if the mouse pointer is over the resize margin area and set
     458             * the resize mode accordingly
     459             */
     460            if (y <= options.resizeMargin)
    225461                resize = 'n';
    226             else if (y >= selection.height - resizeMargin)
     462            else if (y >= selection.height - options.resizeMargin)
    227463                resize = 's';
    228             if (x <= resizeMargin)
     464            if (x <= options.resizeMargin)
    229465                resize += 'w';
    230             else if (x >= selection.width - resizeMargin)
     466            else if (x >= selection.width - options.resizeMargin)
    231467                resize += 'e';
    232468        }
    233469
     
    237473            $areaOpera.toggle();
    238474    }
    239475
     476    /**
     477     * Document mouseup event handler
     478     *
     479     * @param event
     480     *            The event object
     481     */
    240482    function docMouseUp(event) {
     483        /* Set back the default cursor */
    241484        $('body').css('cursor', '');
    242 
     485        /*
     486         * If autoHide is enabled, or if the selection has zero width/height,
     487         * hide the selection and the outer area
     488         */
    243489        if (options.autoHide || selection.width * selection.height == 0)
    244490            hide($box.add($outer), function () { $(this).hide(); });
    245491
    246         options.onSelectEnd(img, getSelection());
    247 
    248492        $(document).unbind('mousemove', selectingMouseMove);
    249493        $box.mousemove(areaMouseMove);
     494       
     495        options.onSelectEnd(img, getSelection());
    250496    }
    251497
     498    /**
     499     * Selection area mousedown event handler
     500     *
     501     * @param event
     502     *            The event object
     503     * @return false
     504     */
    252505    function areaMouseDown(event) {
    253506        if (event.which != 1) return false;
    254507
    255508        adjust();
    256509
    257510        if (resize) {
     511            /* Resize mode is in effect */
    258512            $('body').css('cursor', resize + '-resize');
    259513
    260514            x1 = viewX(selection[/w/.test(resize) ? 'x2' : 'x1']);
     
    284538        return false;
    285539    }
    286540
    287     function aspectRatioXY() {
    288         x2 = max(left, min(left + imgWidth,
    289             x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1)));
    290 
    291         y2 = round(max(top, min(top + imgHeight,
    292             y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1))));
    293         x2 = round(x2);
     541    /**
     542     * Adjust the x2/y2 coordinates to maintain aspect ratio (if defined)
     543     *
     544     * @param xFirst
     545     *            If set to <code>true</code>, calculate x2 first. Otherwise,
     546     *            calculate y2 first.
     547     */
     548    function fixAspectRatio(xFirst) {
     549        if (aspectRatio)
     550            if (xFirst) {
     551                x2 = max(left, min(left + imgWidth,
     552                    x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1)));   
     553                y2 = round(max(top, min(top + imgHeight,
     554                    y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1))));
     555                x2 = round(x2);
     556            }
     557            else {
     558                y2 = max(top, min(top + imgHeight,
     559                    y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1)));
     560                x2 = round(max(left, min(left + imgWidth,
     561                    x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1))));
     562                y2 = round(y2);
     563            }
    294564    }
    295565
    296     function aspectRatioYX() {
    297         y2 = max(top, min(top + imgHeight,
    298             y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1)));
    299         x2 = round(max(left, min(left + imgWidth,
    300             x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1))));
    301         y2 = round(y2);
    302     }
    303 
     566    /**
     567     * Resize the selection area respecting the minimum/maximum dimensions and
     568     * aspect ratio
     569     */
    304570    function doResize() {
    305         if (abs(x2 - x1) < options.minWidth) {
    306             x2 = x1 - options.minWidth * (x2 < x1 || -1);
     571        /*
     572         * Make sure the top left corner of the selection area stays within
     573         * image boundaries (it might not if the image source was dynamically
     574         * changed).
     575         */
     576        x1 = min(x1, left + imgWidth);
     577        y1 = min(y1, top + imgHeight);
     578       
     579        if (abs(x2 - x1) < minWidth) {
     580            /* Selection width is smaller than minWidth */
     581            x2 = x1 - minWidth * (x2 < x1 || -1);
    307582
    308583            if (x2 < left)
    309                 x1 = left + options.minWidth;
     584                x1 = left + minWidth;
    310585            else if (x2 > left + imgWidth)
    311                 x1 = left + imgWidth - options.minWidth;
     586                x1 = left + imgWidth - minWidth;
    312587        }
    313588
    314         if (abs(y2 - y1) < options.minHeight) {
    315             y2 = y1 - options.minHeight * (y2 < y1 || -1);
     589        if (abs(y2 - y1) < minHeight) {
     590            /* Selection height is smaller than minHeight */
     591            y2 = y1 - minHeight * (y2 < y1 || -1);
    316592
    317593            if (y2 < top)
    318                 y1 = top + options.minHeight;
     594                y1 = top + minHeight;
    319595            else if (y2 > top + imgHeight)
    320                 y1 = top + imgHeight - options.minHeight;
     596                y1 = top + imgHeight - minHeight;
    321597        }
    322598
    323599        x2 = max(left, min(x2, left + imgWidth));
    324600        y2 = max(top, min(y2, top + imgHeight));
     601       
     602        fixAspectRatio(abs(x2 - x1) < abs(y2 - y1) * aspectRatio);
    325603
    326         if (aspectRatio)
    327             if (abs(x2 - x1) / aspectRatio > abs(y2 - y1))
    328                 aspectRatioYX();
    329             else
    330                 aspectRatioXY();
    331 
    332         if (abs(x2 - x1) > options.maxWidth) {
    333             x2 = x1 - options.maxWidth * (x2 < x1 || -1);
    334             if (aspectRatio) aspectRatioYX();
     604        if (abs(x2 - x1) > maxWidth) {
     605            /* Selection width is greater than maxWidth */
     606            x2 = x1 - maxWidth * (x2 < x1 || -1);
     607            fixAspectRatio();
    335608        }
    336609
    337         if (abs(y2 - y1) > options.maxHeight) {
    338             y2 = y1 - options.maxHeight * (y2 < y1 || -1);
    339             if (aspectRatio) aspectRatioXY();
     610        if (abs(y2 - y1) > maxHeight) {
     611            /* Selection height is greater than maxHeight */
     612            y2 = y1 - maxHeight * (y2 < y1 || -1);
     613            fixAspectRatio(true);
    340614        }
    341615
    342616        selection = { x1: selX(min(x1, x2)), x2: selX(max(x1, x2)),
     
    348622        options.onSelectChange(img, getSelection());
    349623    }
    350624
     625    /**
     626     * Mousemove event handler triggered when the user is selecting an area
     627     *
     628     * @param event
     629     *            The event object
     630     * @return false
     631     */
    351632    function selectingMouseMove(event) {
    352633        x2 = resize == '' || /w|e/.test(resize) || aspectRatio ? evX(event) : viewX(selection.x2);
    353634        y2 = resize == '' || /n|s/.test(resize) || aspectRatio ? evY(event) : viewY(selection.y2);
    354635
    355636        doResize();
    356637
    357         return false;
    358 
     638        return false;       
    359639    }
    360640
     641    /**
     642     * Move the selection area
     643     *
     644     * @param newX1
     645     *            New viewport X1
     646     * @param newY1
     647     *            New viewport Y1
     648     */
    361649    function doMove(newX1, newY1) {
    362650        x2 = (x1 = newX1) + selection.width;
    363651        y2 = (y1 = newY1) + selection.height;
    364652
    365         selection = $.extend(selection, { x1: selX(x1), y1: selY(y1),
    366             x2: selX(x2), y2: selY(y2) });
     653        $.extend(selection, { x1: selX(x1), y1: selY(y1), x2: selX(x2),
     654            y2: selY(y2) });
    367655
    368656        update();
    369657
    370658        options.onSelectChange(img, getSelection());
    371659    }
    372660
     661    /**
     662     * Mousemove event handler triggered when the selection area is being moved
     663     *
     664     * @param event
     665     *            The event object
     666     * @return false
     667     */
    373668    function movingMouseMove(event) {
    374669        x1 = max(left, min(startX + evX(event), left + imgWidth - selection.width));
    375670        y1 = max(top, min(startY + evY(event), top + imgHeight - selection.height));
    376671
    377672        doMove(x1, y1);
    378673
    379         event.preventDefault();
    380 
     674        event.preventDefault();     
    381675        return false;
    382676    }
    383677
     678    /**
     679     * Start selection
     680     */
    384681    function startSelection() {
     682        $(document).unbind('mousemove', startSelection);
    385683        adjust();
    386684
    387685        x2 = x1;
    388         y2 = y1;
    389 
     686        y2 = y1;       
    390687        doResize();
    391688
    392689        resize = '';
    393690
    394691        if ($outer.is(':not(:visible)'))
     692            /* Show the plugin elements */
    395693            $box.add($outer).hide().fadeIn(options.fadeSpeed||0);
    396694
    397695        shown = true;
     
    403701        options.onSelectStart(img, getSelection());
    404702    }
    405703
     704    /**
     705     * Cancel selection
     706     */
    406707    function cancelSelection() {
    407         $(document).unbind('mousemove', startSelection);
     708        $(document).unbind('mousemove', startSelection)
     709            .unbind('mouseup', cancelSelection);
    408710        hide($box.add($outer));
    409 
    410         selection = { x1: selX(x1), y1: selY(y1), x2: selX(x1), y2: selY(y1),
    411                 width: 0, height: 0 };
    412 
     711       
     712        setSelection(selX(x1), selY(y1), selX(x1), selY(y1));
     713       
    413714        options.onSelectChange(img, getSelection());
    414715        options.onSelectEnd(img, getSelection());
    415716    }
    416717
     718    /**
     719     * Image mousedown event handler
     720     *
     721     * @param event
     722     *            The event object
     723     * @return false
     724     */
    417725    function imgMouseDown(event) {
     726        /* Ignore the event if animation is in progress */
    418727        if (event.which != 1 || $outer.is(':animated')) return false;
    419728
    420729        adjust();
    421730        startX = x1 = evX(event);
    422731        startY = y1 = evY(event);
    423732
    424         $(document).one('mousemove', startSelection)
    425             .one('mouseup', cancelSelection);
     733        /* Selection will start when the mouse is moved */
     734        $(document).mousemove(startSelection).mouseup(cancelSelection);
    426735
    427736        return false;
    428737    }
    429 
    430     function parentScroll() {
     738   
     739    /**
     740     * Window resize event handler
     741     */
     742    function windowResize() {
    431743        doUpdate(false);
    432744    }
    433745
     746    /**
     747     * Image load event handler. This is the final part of the initialization
     748     * process.
     749     */
    434750    function imgLoad() {
    435751        imgLoaded = true;
    436752
     753        /* Set options */
    437754        setOptions(options = $.extend({
    438755            classPrefix: 'imgareaselect',
    439756            movable: true,
     757            parent: 'body',
    440758            resizable: true,
    441             parent: 'body',
     759            resizeMargin: 10,
    442760            onInit: function () {},
    443761            onSelectStart: function () {},
    444762            onSelectChange: function () {},
     
    454772            $box.add($outer).hide().fadeIn(options.fadeSpeed||0);
    455773        }
    456774
     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         */
    457780        setTimeout(function () { options.onInit(img, getSelection()); }, 0);
    458781    }
    459782
     783    /**
     784     * Document keypress event handler
     785     *
     786     * @param event
     787     *            The event object
     788     * @return false
     789     */
    460790    var docKeyPress = function(event) {
    461         var k = options.keys, d, t, key = event.keyCode || event.which;
    462 
     791        var k = options.keys, d, t, key = event.keyCode;
     792       
    463793        d = !isNaN(k.alt) && (event.altKey || event.originalEvent.altKey) ? k.alt :
    464794            !isNaN(k.ctrl) && event.ctrlKey ? k.ctrl :
    465795            !isNaN(k.shift) && event.shiftKey ? k.shift :
     
    469799            (k.ctrl == 'resize' && event.ctrlKey) ||
    470800            (k.alt == 'resize' && (event.altKey || event.originalEvent.altKey)))
    471801        {
     802            /* Resize selection */
     803           
    472804            switch (key) {
    473805            case 37:
     806                /* Left */
    474807                d = -d;
    475808            case 39:
     809                /* Right */
    476810                t = max(x1, x2);
    477811                x1 = min(x1, x2);
    478812                x2 = max(t + d, x1);
    479                 if (aspectRatio) aspectRatioYX();
     813                fixAspectRatio();
    480814                break;
    481815            case 38:
     816                /* Up */
    482817                d = -d;
    483818            case 40:
     819                /* Down */
    484820                t = max(y1, y2);
    485821                y1 = min(y1, y2);
    486822                y2 = max(t + d, y1);
    487                 if (aspectRatio) aspectRatioXY();
     823                fixAspectRatio(true);
    488824                break;
    489825            default:
    490826                return;
     
    493829            doResize();
    494830        }
    495831        else {
     832            /* Move selection */
     833           
    496834            x1 = min(x1, x2);
    497835            y1 = min(y1, y2);
    498836
    499837            switch (key) {
    500838            case 37:
     839                /* Left */
    501840                doMove(max(x1 - d, left), y1);
    502841                break;
    503842            case 38:
     843                /* Up */
    504844                doMove(x1, max(y1 - d, top));
    505845                break;
    506846            case 39:
     847                /* Right */
    507848                doMove(x1 + min(d, imgWidth - selX(x2)), y1);
    508849                break;
    509850            case 40:
     851                /* Down */
    510852                doMove(x1, y1 + min(d, imgHeight - selY(y2)));
    511853                break;
    512854            default:
     
    517859        return false;
    518860    };
    519861
     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     */
    520871    function styleOptions($elem, props) {
    521872        for (option in props)
    522873            if (options[option] !== undefined)
    523874                $elem.css(props[option], options[option]);
    524875    }
    525876
     877    /**
     878     * Set plugin options
     879     *
     880     * @param newOptions
     881     *            The new options object
     882     */
    526883    function setOptions(newOptions) {
    527884        if (newOptions.parent)
    528885            ($parent = $(newOptions.parent)).append($box.add($outer));
     886       
     887        /* Merge the new options with the existing ones */
     888        $.extend(options, newOptions);
    529889
    530         options = $.extend(options, newOptions);
    531 
    532890        adjust();
    533891
    534892        if (newOptions.handles != null) {
     893            /* Recreate selection area handles */
    535894            $handles.remove();
    536895            $handles = $([]);
    537896
     
    539898
    540899            while (i--)
    541900                $handles = $handles.add(div());
    542 
     901           
     902            /* Add a class to handles and set the CSS properties */
    543903            $handles.addClass(options.classPrefix + '-handle').css({
    544904                position: 'absolute',
     905                /*
     906                 * The font-size property needs to be set to zero, otherwise
     907                 * Internet Explorer makes the handles too large
     908                 */
    545909                fontSize: 0,
    546910                zIndex: zIndex + 1 || 1
    547911            });
    548 
    549             if (!parseInt($handles.css('width')))
     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)
    550918                $handles.width(5).height(5);
    551 
     919           
     920            /*
     921             * If the borderWidth option is in use, add a solid border to
     922             * handles
     923             */
    552924            if (o = options.borderWidth)
    553925                $handles.css({ borderWidth: o, borderStyle: 'solid' });
    554926
     927            /* Apply other style options */
    555928            styleOptions($handles, { borderColor1: 'border-color',
    556929                borderColor2: 'background-color',
    557930                borderOpacity: 'opacity' });
    558931        }
    559932
     933        /* Calculate scale factors */
    560934        scaleX = options.imageWidth / imgWidth || 1;
    561935        scaleY = options.imageHeight / imgHeight || 1;
    562936
     937        /* Set selection */
    563938        if (newOptions.x1 != null) {
    564939            setSelection(newOptions.x1, newOptions.y1, newOptions.x2,
    565                     newOptions.y2);
     940                newOptions.y2);
    566941            newOptions.show = !newOptions.hide;
    567942        }
    568943
    569944        if (newOptions.keys)
     945            /* Enable keyboard support */
    570946            options.keys = $.extend({ shift: 1, ctrl: 'resize' },
    571947                newOptions.keys);
    572948
     949        /* Add classes to plugin elements */
    573950        $outer.addClass(options.classPrefix + '-outer');
    574951        $area.addClass(options.classPrefix + '-selection');
    575952        for (i = 0; i++ < 4;)
    576953            $($border[i-1]).addClass(options.classPrefix + '-border' + i);
    577954
     955        /* Apply style options */
    578956        styleOptions($area, { selectionColor: 'background-color',
    579957            selectionOpacity: 'opacity' });
    580958        styleOptions($border, { borderOpacity: 'opacity',
     
    586964        if (o = options.borderColor2)
    587965            $($border[1]).css({ borderStyle: 'dashed', borderColor: o });
    588966
    589         $box.append($area.add($border).add($handles).add($areaOpera));
     967        /* Append all the selection area elements to the container box */
     968        $box.append($area.add($border).add($areaOpera).add($handles));
    590969
    591970        if ($.browser.msie) {
    592971            if (o = $outer.css('filter').match(/opacity=([0-9]+)/))
     
    594973            if (o = $border.css('filter').match(/opacity=([0-9]+)/))
    595974                $border.css('opacity', o[1]/100);
    596975        }
    597 
     976       
    598977        if (newOptions.hide)
    599978            hide($box.add($outer));
    600979        else if (newOptions.show && imgLoaded) {
     
    603982            doUpdate();
    604983        }
    605984
     985        /* Calculate the aspect ratio factor */
    606986        aspectRatio = (d = (options.aspectRatio || '').split(/:/))[0] / d[1];
    607987
     988        $img.add($outer).unbind('mousedown', imgMouseDown);
     989       
    608990        if (options.disable || options.enable === false) {
     991            /* Disable the plugin */
    609992            $box.unbind('mousemove', areaMouseMove).unbind('mousedown', areaMouseDown);
    610             $img.add($outer).unbind('mousedown', imgMouseDown);
    611             $(window).unbind('resize', parentScroll);
    612             $img.add($img.parents()).unbind('scroll', parentScroll);
     993            $(window).unbind('resize', windowResize);
    613994        }
    614         else if (options.enable || options.disable === false) {
    615             if (options.resizable || options.movable)
    616                 $box.mousemove(areaMouseMove).mousedown(areaMouseDown);
     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            }
    6171003
    6181004            if (!options.persistent)
    6191005                $img.add($outer).mousedown(imgMouseDown);
    620             $(window).resize(parentScroll);
    621             $img.add($img.parents()).scroll(parentScroll);
    6221006        }
    623 
     1007       
    6241008        options.enable = options.disable = undefined;
    6251009    }
    626 
     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     */
    6271031    this.getOptions = function () { return options; };
    628 
     1032   
     1033    /**
     1034     * Set plugin options
     1035     *
     1036     * @param newOptions
     1037     *            The new options object
     1038     */
    6291039    this.setOptions = setOptions;
    630 
     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     */
    6311049    this.getSelection = getSelection;
    632 
     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     */
    6331066    this.setSelection = setSelection;
    634 
     1067   
     1068    /**
     1069     * Update plugin elements
     1070     *
     1071     * @param resetKeyPress
     1072     *            If set to <code>false</code>, this instance's keypress
     1073     *            event handler is not activated
     1074     */
    6351075    this.update = doUpdate;
    6361076
     1077    /*
     1078     * Traverse the image's parent elements (up to <body>) and find the
     1079     * highest z-index
     1080     */
    6371081    $p = $img;
    6381082
    639     while ($p.length && !$p.is('body')) {
    640         if (!isNaN($p.css('z-index')) && $p.css('z-index') > zIndex)
    641             zIndex = $p.css('z-index');
     1083    while ($p.length) {
     1084        zIndex = max(zIndex,
     1085            !isNaN($p.css('z-index')) ? $p.css('z-index') : zIndex);
     1086        /* Also check if any of the ancestor elements has fixed position */
    6421087        if ($p.css('position') == 'fixed')
    6431088            position = 'fixed';
    6441089
    645         $p = $p.parent();
     1090        $p = $p.parent(':not(body)');
    6461091    }
     1092   
     1093    /*
     1094     * If z-index is given as an option, it overrides the one found by the
     1095     * above loop
     1096     */
     1097    zIndex = options.zIndex || zIndex;
    6471098
    648     if (!isNaN(options.zIndex))
    649         zIndex = options.zIndex;
    650 
    6511099    if ($.browser.msie)
    6521100        $img.attr('unselectable', 'on');
    6531101
     1102    /*
     1103     * In MSIE and WebKit, we need to use the keydown event instead of keypress
     1104     */
    6541105    $.imgAreaSelect.keyPress = $.browser.msie ||
    6551106        $.browser.safari ? 'keydown' : 'keypress';
    6561107
     1108    /*
     1109     * There is a bug affecting the CSS cursor property in Opera (observed in
     1110     * versions up to 10.00) that prevents the cursor from being updated unless
     1111     * the mouse leaves and enters the element again. To trigger the mouseover
     1112     * event, we're adding an additional div to $box and we're going to toggle
     1113     * it when mouse moves inside the selection area.
     1114     */
    6571115    if ($.browser.opera)
    6581116        $areaOpera = div().css({ width: '100%', height: '100%',
    6591117            position: 'absolute', zIndex: zIndex + 2 || 2 });
    6601118
     1119    /*
     1120     * We initially set visibility to "hidden" as a workaround for a weird
     1121     * behaviour observed in Google Chrome 1.0.154.53 (on Windows XP). Normally
     1122     * we would just set display to "none", but, for some reason, if we do so
     1123     * then Chrome refuses to later display the element with .show() or
     1124     * .fadeIn().
     1125     */
    6611126    $box.add($outer).css({ visibility: 'hidden', position: position,
    6621127        overflow: 'hidden', zIndex: zIndex || '0' });
    6631128    $box.css({ zIndex: zIndex + 2 || 2 });
    664     $area.add($border).css({ position: 'absolute' });
    665 
     1129    $area.add($border).css({ position: 'absolute', fontSize: 0 });
     1130   
     1131    /*
     1132     * If the image has been fully loaded, or if it is not really an image (eg.
     1133     * a div), call imgLoad() immediately; otherwise, bind it to be called once
     1134     * on image load event.
     1135     */
    6661136    img.complete || img.readyState == 'complete' || !$img.is('img') ?
    6671137        imgLoad() : $img.one('load', imgLoad);
    6681138
     1139    /*
     1140     * MSIE 9.0 doesn't always fire the image load event -- resetting the src
     1141     * attribute seems to trigger it.
     1142     */   
     1143    if ($.browser.msie && $.browser.version >= 9)
     1144        img.src = img.src;
    6691145};
    6701146
     1147/**
     1148 * Invoke imgAreaSelect on a jQuery object containing the image(s)
     1149 *
     1150 * @param options
     1151 *            Options object
     1152 * @return The jQuery object or a reference to imgAreaSelect instance (if the
     1153 *         <code>instance</code> option was specified)
     1154 */
    6711155$.fn.imgAreaSelect = function (options) {
    6721156    options = options || {};
    6731157
    6741158    this.each(function () {
    675         if ($(this).data('imgAreaSelect'))
    676             $(this).data('imgAreaSelect').setOptions(options);
    677         else {
     1159        /* Is there already an imgAreaSelect instance bound to this element? */
     1160        if ($(this).data('imgAreaSelect')) {
     1161            /* Yes there is -- is it supposed to be removed? */
     1162            if (options.remove) {
     1163                /* Remove the plugin */
     1164                $(this).data('imgAreaSelect').remove();
     1165                $(this).removeData('imgAreaSelect');
     1166            }
     1167            else
     1168                /* Reset options */
     1169                $(this).data('imgAreaSelect').setOptions(options);
     1170        }
     1171        else if (!options.remove) {
     1172            /* No exising instance -- create a new one */
     1173           
     1174            /*
     1175             * If neither the "enable" nor the "disable" option is present, add
     1176             * "enable" as the default
     1177             */
    6781178            if (options.enable === undefined && options.disable === undefined)
    6791179                options.enable = true;
    6801180
    6811181            $(this).data('imgAreaSelect', new $.imgAreaSelect(this, options));
    6821182        }
    6831183    });
    684 
     1184   
    6851185    if (options.instance)
     1186        /*
     1187         * Return the imgAreaSelect instance bound to the first element in the
     1188         * set
     1189         */
    6861190        return $(this).data('imgAreaSelect');
    6871191
    6881192    return this;