/dev/trunk

To get this branch, use:
bzr branch http://darksoft.org/webbzr/dev/trunk

« back to all changes in this revision

Viewing changes to includes/cropper/cropper.js

  • Committer: Suren A. Chilingaryan
  • Date: 2008-04-02 10:23:22 UTC
  • Revision ID: csa@dside.dyndns.org-20080402102322-okib92sicg2dx3o3
Initial import

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/**
 
2
 * Image Cropper (v. 1.2.0 - 2006-10-30 )
 
3
 * Copyright (c) 2006 David Spurr (http://www.defusion.org.uk/)
 
4
 * 
 
5
 * The image cropper provides a way to draw a crop area on an image and capture
 
6
 * the coordinates of the drawn crop area.
 
7
 * 
 
8
 * Features include:
 
9
 *              - Based on Prototype and Scriptaculous
 
10
 *              - Image editing package styling, the crop area functions and looks 
 
11
 *                like those found in popular image editing software
 
12
 *              - Dynamic inclusion of required styles
 
13
 *              - Drag to draw areas
 
14
 *              - Shift drag to draw/resize areas as squares
 
15
 *              - Selection area can be moved 
 
16
 *              - Seleciton area can be resized using resize handles
 
17
 *              - Allows dimension ratio limited crop areas
 
18
 *              - Allows minimum dimension crop areas
 
19
 *              - Allows maximum dimesion crop areas
 
20
 *              - If both min & max dimension options set to the same value for a single axis,then the cropper will not 
 
21
 *                display the resize handles as appropriate (when min & max dimensions are passed for both axes this
 
22
 *                results in a 'fixed size' crop area)
 
23
 *              - Allows dynamic preview of resultant crop ( if minimum width & height are provided ), this is
 
24
 *                implemented as a subclass so can be excluded when not required
 
25
 *              - Movement of selection area by arrow keys ( shift + arrow key will move selection area by
 
26
 *                10 pixels )
 
27
 *              - All operations stay within bounds of image
 
28
 *              - All functionality & display compatible with most popular browsers supported by Prototype:
 
29
 *                      PC:     IE 7, 6 & 5.5, Firefox 1.5, Opera 8.5 (see known issues) & 9.0b
 
30
 *                      MAC: Camino 1.0, Firefox 1.5, Safari 2.0
 
31
 * 
 
32
 * Requires:
 
33
 *              - Prototype v. 1.5.0_rc0 > (as packaged with Scriptaculous 1.6.1)
 
34
 *              - Scriptaculous v. 1.6.1 > modules: builder, dragdrop 
 
35
 *              
 
36
 * Known issues:
 
37
 *              - Safari animated gifs, only one of each will animate, this seems to be a known Safari issue
 
38
 * 
 
39
 *              - After drawing an area and then clicking to start a new drag in IE 5.5 the rendered height 
 
40
 *        appears as the last height until the user drags, this appears to be the related to the error 
 
41
 *        that the forceReRender() method fixes for IE 6, i.e. IE 5.5 is not redrawing the box properly.
 
42
 * 
 
43
 *              - Lack of CSS opacity support in Opera before version 9 mean we disable those style rules, these 
 
44
 *                could be fixed by using PNGs with transparency if Opera 8.5 support is high priority for you
 
45
 * 
 
46
 *              - Marching ants keep reloading in IE <6 (not tested in IE7), it is a known issue in IE and I have 
 
47
 *        found no viable workarounds that can be included in the release. If this really is an issue for you
 
48
 *        either try this post: http://mir.aculo.us/articles/2005/08/28/internet-explorer-and-ajax-image-caching-woes
 
49
 *        or uncomment the 'FIX MARCHING ANTS IN IE' rules in the CSS file
 
50
 *              
 
51
 *              - Styling & borders on image, any CSS styling applied directly to the image itself (floats, borders, padding, margin, etc.) will 
 
52
 *                cause problems with the cropper. The use of a wrapper element to apply these styles to is recommended.
 
53
 * 
 
54
 *              - overflow: auto or overflow: scroll on parent will cause cropper to burst out of parent in IE and Opera (maybe Mac browsers too)
 
55
 *                I'm not sure why yet.
 
56
 * 
 
57
 * Usage:
 
58
 *              See Cropper.Img & Cropper.ImgWithPreview for usage details
 
59
 * 
 
60
 * Changelog:
 
61
 * v1.2.0 - 2006-10-30
 
62
 *              + Added id to the preview image element using 'imgCrop_[originalImageID]'
 
63
 *      * #00001 - Fixed bug: Doesn't account for scroll offsets
 
64
 *      * #00009 - Fixed bug: Placing the cropper inside differently positioned elements causes incorrect co-ordinates and display
 
65
 *      * #00013 - Fixed bug: I-bar cursor appears on drag plane
 
66
 *      * #00014 - Fixed bug: If ID for image tag is not found in document script throws error
 
67
 *      * Fixed bug with drag start co-ordinates if wrapper element has moved in browser (e.g. dragged to a new position)
 
68
 *      * Fixed bug with drag start co-ordinates if image contained in a wrapper with scrolling - this may be buggy if image 
 
69
 *                has other ancestors with scrolling applied (except the body)
 
70
 *      * #00015 - Fixed bug: When cropper removed and then reapplied onEndCrop callback gets called multiple times, solution suggestion from Bill Smith
 
71
 *      * Various speed increases & code cleanup which meant improved performance in Mac - which allowed removal of different overlay methods for
 
72
 *        IE and all other browsers, which led to a fix for:
 
73
 *              * #00010 - Fixed bug: Select area doesn't adhere to image size when image resized using img attributes
 
74
 *      - #00006 - Removed default behaviour of automatically setting a ratio when both min width & height passed, the ratioDimensions must be passed in
 
75
 *              + #00005 - Added ability to set maximum crop dimensions, if both min & max set as the same value then we'll get a fixed cropper size on the axes as appropriate
 
76
 *        and the resize handles will not be displayed as appropriate
 
77
 *              * Switched keydown for keypress for moving select area with cursor keys (makes for nicer action) - doesn't appear to work in Safari
 
78
 * 
 
79
 * v1.1.3 - 2006-08-21
 
80
 *              * Fixed wrong cursor on western handle in CSS
 
81
 *              + #00008 & #00003 - Added feature: Allow to set dimensions & position for cropper on load
 
82
 *      * #00002 - Fixed bug: Pressing 'remove cropper' twice removes image in IE
 
83
 * 
 
84
 * v1.1.2 - 2006-06-09
 
85
 *              * Fixed bugs with ratios when GCD is low (patch submitted by Andy Skelton)
 
86
 * 
 
87
 * v1.1.1 - 2006-06-03
 
88
 *              * Fixed bug with rendering issues fix in IE 5.5
 
89
 *              * Fixed bug with endCrop callback issues once cropper had been removed & reset in IE
 
90
 * 
 
91
 * v1.1.0 - 2006-06-02
 
92
 *              * Fixed bug with IE constantly trying to reload select area background image
 
93
 *              * Applied more robust fix to Safari & IE rendering issues
 
94
 *              + Added method to reset parameters - useful for when dynamically changing img cropper attached to
 
95
 *              + Added method to remove cropper from image
 
96
 * 
 
97
 * v1.0.0 - 2006-05-18 
 
98
 *              + Initial verison
 
99
 * 
 
100
 * 
 
101
 * Copyright (c) 2006, David Spurr (http://www.defusion.org.uk/)
 
102
 * All rights reserved.
 
103
 * 
 
104
 * 
 
105
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 
106
 * 
 
107
 *     * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 
108
 *     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 
109
 *     * Neither the name of the David Spurr nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
 
110
 * 
 
111
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
112
 * 
 
113
 * http://www.opensource.org/licenses/bsd-license.php
 
114
 * 
 
115
 * See scriptaculous.js for full scriptaculous licence
 
116
 */
 
117
 
 
118
/**
 
119
 * Extend the Draggable class to allow us to pass the rendering
 
120
 * down to the Cropper object.
 
121
 */
 
122
var CropDraggable = Class.create();
 
123
 
 
124
Object.extend( Object.extend( CropDraggable.prototype, Draggable.prototype), {
 
125
        
 
126
        initialize: function(element) {
 
127
                this.options = Object.extend(
 
128
                        {
 
129
                                /**
 
130
                                 * The draw method to defer drawing to
 
131
                                 */
 
132
                                drawMethod: function() {}
 
133
                        }, 
 
134
                        arguments[1] || {}
 
135
                );
 
136
 
 
137
                this.element = $(element);
 
138
 
 
139
                this.handle = this.element;
 
140
 
 
141
                this.delta    = this.currentDelta();
 
142
                this.dragging = false;   
 
143
 
 
144
                this.eventMouseDown = this.initDrag.bindAsEventListener(this);
 
145
                Event.observe(this.handle, "mousedown", this.eventMouseDown);
 
146
 
 
147
                Draggables.register(this);
 
148
        },
 
149
        
 
150
        /**
 
151
         * Defers the drawing of the draggable to the supplied method
 
152
         */
 
153
        draw: function(point) {
 
154
                var pos = Position.cumulativeOffset(this.element);
 
155
                var d = this.currentDelta();
 
156
                pos[0] -= d[0]; 
 
157
                pos[1] -= d[1];
 
158
                                
 
159
                var p = [0,1].map(function(i) { 
 
160
                        return (point[i]-pos[i]-this.offset[i]) 
 
161
                }.bind(this));
 
162
                                
 
163
                this.options.drawMethod( p );
 
164
        }
 
165
        
 
166
});
 
167
 
 
168
 
 
169
/**
 
170
 * The Cropper object, this will attach itself to the provided image by wrapping it with 
 
171
 * the generated xHTML structure required by the cropper.
 
172
 * 
 
173
 * Usage:
 
174
 *      @param obj Image element to attach to
 
175
 *      @param obj Optional options:
 
176
 *              - ratioDim obj 
 
177
 *                      The pixel dimensions to apply as a restrictive ratio, with properties x & y
 
178
 * 
 
179
 *              - minWidth int 
 
180
 *                      The minimum width for the select area in pixels
 
181
 * 
 
182
 *              - minHeight     int 
 
183
 *                      The mimimum height for the select area in pixels
 
184
 * 
 
185
 *              - maxWidth int
 
186
 *                      The maximum width for the select areas in pixels (if both minWidth & maxWidth set to same the width of the cropper will be fixed)
 
187
 * 
 
188
 *              - maxHeight int
 
189
 *                      The maximum height for the select areas in pixels (if both minHeight & maxHeight set to same the height of the cropper will be fixed)
 
190
 * 
 
191
 *              - displayOnInit int 
 
192
 *                      Whether to display the select area on initialisation, only used when providing minimum width & height or ratio
 
193
 * 
 
194
 *              - onEndCrop func
 
195
 *                      The callback function to provide the crop details to on end of a crop (see below)
 
196
 * 
 
197
 *              - captureKeys boolean
 
198
 *                      Whether to capture the keys for moving the select area, as these can cause some problems at the moment
 
199
 * 
 
200
 *              - onloadCoords obj
 
201
 *                      A coordinates object with properties x1, y1, x2 & y2; for the coordinates of the select area to display onload
 
202
 *      
 
203
 *----------------------------------------------
 
204
 * 
 
205
 * The callback function provided via the onEndCrop option should accept the following parameters:
 
206
 *              - coords obj
 
207
 *                      The coordinates object with properties x1, y1, x2 & y2; for the coordinates of the select area
 
208
 * 
 
209
 *              - dimensions obj
 
210
 *                      The dimensions object with properites width & height; for the dimensions of the select area
 
211
 *              
 
212
 *
 
213
 *              Example:
 
214
 *                      function onEndCrop( coords, dimensions ) {
 
215
 *                              $( 'x1' ).value         = coords.x1;
 
216
 *                              $( 'y1' ).value         = coords.y1;
 
217
 *                              $( 'x2' ).value         = coords.x2;
 
218
 *                              $( 'y2' ).value         = coords.y2;
 
219
 *                              $( 'width' ).value      = dimensions.width;
 
220
 *                              $( 'height' ).value     = dimensions.height;
 
221
 *                      }
 
222
 * 
 
223
 */
 
224
var Cropper = {};
 
225
Cropper.Img = Class.create();
 
226
Cropper.Img.prototype = {
 
227
        
 
228
        /**
 
229
         * Initialises the class
 
230
         * 
 
231
         * @access public
 
232
         * @param obj Image element to attach to
 
233
         * @param obj Options
 
234
         * @return void
 
235
         */
 
236
        initialize: function(element, options) {
 
237
                this.options = Object.extend(
 
238
                        {
 
239
                                /**
 
240
                                 * @var obj
 
241
                                 * The pixel dimensions to apply as a restrictive ratio
 
242
                                 */
 
243
                                ratioDim: { x: 0, y: 0 },
 
244
                                /**
 
245
                                 * @var int
 
246
                                 * The minimum pixel width, also used as restrictive ratio if min height passed too
 
247
                                 */
 
248
                                minWidth:               0,
 
249
                                /**
 
250
                                 * @var int
 
251
                                 * The minimum pixel height, also used as restrictive ratio if min width passed too
 
252
                                 */
 
253
                                minHeight:              0,
 
254
                                /**
 
255
                                 * @var boolean
 
256
                                 * Whether to display the select area on initialisation, only used when providing minimum width & height or ratio
 
257
                                 */
 
258
                                displayOnInit:  false,
 
259
                                /**
 
260
                                 * @var function
 
261
                                 * The call back function to pass the final values to
 
262
                                 */
 
263
                                onEndCrop: Prototype.emptyFunction,
 
264
                                /**
 
265
                                 * @var function
 
266
                                 * The call back function to be called if crop is canceled
 
267
                                 */
 
268
                                onCancelCrop: Prototype.emptyFunction,
 
269
                                /**
 
270
                                 * @var function
 
271
                                 * The call back function to pass the final values to
 
272
                                 */
 
273
                                onDblClick: Prototype.emptyFunction,
 
274
                                /**
 
275
                                 * @var function
 
276
                                 * The call back function on mouse wheel scrolling
 
277
                                 */
 
278
                                 onMouseScroll: null,
 
279
                                /**
 
280
                                 * @var function
 
281
                                 * The call back function to pass the final values to
 
282
                                 */
 
283
                                onApplyClick: null,
 
284
                                /**
 
285
                                 * @var function
 
286
                                 * The call back to be called on click of save button
 
287
                                 */
 
288
                                 onSaveClick: null,
 
289
                                /**
 
290
                                 * @var function
 
291
                                 * The call back function to handle click event
 
292
                                 */
 
293
                                onClick: null,
 
294
                                /**
 
295
                                 * @var boolean
 
296
                                 * Whether to capture key presses or not
 
297
                                 */
 
298
                                captureKeys: true,
 
299
                                /**
 
300
                                 * @var obj Coordinate object x1, y1, x2, y2
 
301
                                 * The coordinates to optionally display the select area at onload
 
302
                                 */
 
303
                                onloadCoords: null,
 
304
                                /**
 
305
                                 * @var obj tooltips for misc. buttons
 
306
                                 */
 
307
                                tooltips: null,
 
308
                                /**
 
309
                                 * @var int
 
310
                                 * The maximum width for the select areas in pixels (if both minWidth & maxWidth set to same the width of the cropper will be fixed)
 
311
                                 */
 
312
                                maxWidth: 0,
 
313
                                /**
 
314
                                 * @var int
 
315
                                 * The maximum height for the select areas in pixels (if both minHeight & maxHeight set to same the height of the cropper will be fixed)
 
316
                                 */
 
317
                                maxHeight: 0,
 
318
                                /**
 
319
                                 * @var obj Coordinate object left, top, right, bottom
 
320
                                 * The non-selectable margins
 
321
                                 */
 
322
                                margins: { left: 0, top: 0, right: 0, bottom: 0 },
 
323
                                /**
 
324
                                 * @var int
 
325
                                 * If width is below this value, we are selecting whole horizontal range
 
326
                                 */
 
327
                                allWidth: 0,
 
328
                                /**
 
329
                                 * @var int
 
330
                                 * If height is below this value, we are selecting whole vertical range
 
331
                                 */
 
332
                                allHeight: 0,
 
333
                                /**
 
334
                                 * @var boolean
 
335
                                 * Whether to monitor reloading of picture
 
336
                                 */
 
337
                                monitorImage: true,
 
338
                                /**
 
339
                                 * @var boolean
 
340
                                 * Whether the image is already loaded
 
341
                                 */
 
342
                                imageReady: false,
 
343
                                /**
 
344
                                 * @var int
 
345
                                 * Maximal duration of the click event in ms (after dragging is started)
 
346
                                 */
 
347
                                clickDuration: 200
 
348
 
 
349
                        }, 
 
350
                        options || {}
 
351
                );
 
352
                /**
 
353
                 * @var obj
 
354
                 * The img node to attach to
 
355
                 */
 
356
                this.img                        = $( element );
 
357
                /**
 
358
                 * @var obj
 
359
                 * The x & y coordinates of the click point
 
360
                 */
 
361
                this.clickCoords        = { x: 0, y: 0 };
 
362
                /**
 
363
                 * @var boolean
 
364
                 * Whether the user is dragging
 
365
                 */
 
366
                this.dragging           = false;
 
367
                /**
 
368
                 * @var boolean
 
369
                 * Whether the user is resizing
 
370
                 */
 
371
                this.resizing           = false;
 
372
                /**
 
373
                 * @var boolean
 
374
                 * Whether the selected area is present on the screen
 
375
                 */
 
376
                this.selected           = false;
 
377
                /**
 
378
                 * @var boolean
 
379
                 * Whether the selected area is present on the screen
 
380
                 */
 
381
                this.altered            = false;
 
382
                /**
 
383
                 * @var boolean
 
384
                 * Whether the user is on a webKit browser
 
385
                 */
 
386
                this.isWebKit           = /Konqueror|Safari|KHTML/.test( navigator.userAgent );
 
387
                /**
 
388
                 * @var boolean
 
389
                 * Whether the user is on IE
 
390
                 */
 
391
                this.isIE                       = /MSIE/.test( navigator.userAgent );
 
392
                /**
 
393
                 * @var boolean
 
394
                 * Whether the user is on Opera below version 9
 
395
                 */
 
396
                this.isOpera8           = /Opera\s[1-8]/.test( navigator.userAgent );
 
397
                /**
 
398
                 * @var int
 
399
                 * The x ratio 
 
400
                 */
 
401
                this.ratioX                     = 0;
 
402
                /**
 
403
                 * @var int
 
404
                 * The y ratio
 
405
                 */
 
406
                this.ratioY                     = 0;
 
407
                /**
 
408
                 * @var boolean
 
409
                 * Whether we've attached sucessfully
 
410
                 */
 
411
                this.attached           = false;
 
412
                /**
 
413
                 * @var boolean
 
414
                 * Whether we've got a fixed width (if minWidth EQ or GT maxWidth then we have a fixed width
 
415
                 * in the case of minWidth > maxWidth maxWidth wins as the fixed width)
 
416
                 */
 
417
                this.fixedWidth         = ( this.options.maxWidth > 0 && ( this.options.minWidth >= this.options.maxWidth ) );
 
418
                /**
 
419
                 * @var boolean
 
420
                 * Whether we've got a fixed height (if minHeight EQ or GT maxHeight then we have a fixed height
 
421
                 * in the case of minHeight > maxHeight maxHeight wins as the fixed height)
 
422
                 */
 
423
                this.fixedHeight        = ( this.options.maxHeight > 0 && ( this.options.minHeight >= this.options.maxHeight ) );
 
424
                
 
425
                // quit if the image element doesn't exist
 
426
                if( typeof this.img == 'undefined' ) return;
 
427
                                
 
428
                // include the stylesheet               
 
429
                $A( document.getElementsByTagName( 'script' ) ).each( 
 
430
                        function(s) {
 
431
                                if( s.src.match( /cropper\.js/ ) ) {
 
432
                                        var path        = s.src.replace( /cropper\.js(.*)?/, '' );
 
433
                                        // '<link rel="stylesheet" type="text/css" href="' + path + 'cropper.css" media="screen" />';
 
434
                                        var style               = document.createElement( 'link' );
 
435
                                        style.rel               = 'stylesheet';
 
436
                                        style.type              = 'text/css';
 
437
                                        style.href              = path + 'cropper.css';
 
438
                                        style.media     = 'screen';
 
439
                                        document.getElementsByTagName( 'head' )[0].appendChild( style );
 
440
                                }
 
441
                }
 
442
            );   
 
443
        
 
444
                // calculate the ratio when neccessary
 
445
                if( this.options.ratioDim.x > 0 && this.options.ratioDim.y > 0 ) {
 
446
                        var gcd = this.getGCD( this.options.ratioDim.x, this.options.ratioDim.y );
 
447
                        this.ratioX = this.options.ratioDim.x / gcd;
 
448
                        this.ratioY = this.options.ratioDim.y / gcd;
 
449
                        // dump( 'RATIO : ' + this.ratioX + ':' + this.ratioY + '\n' );
 
450
                }
 
451
                                                        
 
452
                // initialise sub classes
 
453
                this.subInitialize();
 
454
 
 
455
                // only load the event observers etc. once the image is loaded
 
456
                // this is done after the subInitialize() call just in case the sub class does anything
 
457
                // that will affect the result of the call to onLoad()
 
458
                if( this.options.imageReady || this.img.complete || this.isWebKit ) {
 
459
                    this.onLoad(); // for some reason Safari seems to support img.complete but returns 'undefined' on the this.img object
 
460
                    if (this.options.monitorImage) Event.observe( this.img, 'load', this.onLoad.bindAsEventListener( this) );
 
461
                } else Event.observe( this.img, 'load', this.onLoad.bindAsEventListener( this) );
 
462
        },
 
463
        
 
464
        /**
 
465
         * Updates tooltips
 
466
         *
 
467
         * @access public
 
468
         * @param object Associative array of tooltips
 
469
         * @return void
 
470
         */
 
471
        setTooltip : function (opts) {
 
472
            if (!this.options.tooltips) this.options.tooltips = new Object();
 
473
            
 
474
            for (var i in opts) {
 
475
                this.options.tooltips[i] = opts[i];
 
476
            }
 
477
            
 
478
            if ((this.applyButton)&&(opts.apply)) {
 
479
                this.applyButton.setAttribute('title', opts.apply);
 
480
            }
 
481
            if ((this.saveButton)&&(opts.save)) {
 
482
                this.saveButton.setAttribute('title', opts.save);
 
483
            }
 
484
        },
 
485
        
 
486
        /**
 
487
         * The Euclidean algorithm used to find the greatest common divisor
 
488
         * 
 
489
         * @acces private
 
490
         * @param int Value 1
 
491
         * @param int Value 2
 
492
         * @return int
 
493
         */
 
494
        getGCD : function( a , b ) {
 
495
                if( b == 0 ) return a;
 
496
                return this.getGCD(b, a % b );
 
497
        },
 
498
        
 
499
        /**
 
500
         * Attaches the cropper to the image once it has loaded
 
501
         * 
 
502
         * @access private
 
503
         * @return void
 
504
         */
 
505
        onLoad: function( ) {
 
506
                /*
 
507
                 * Build the container and all related elements, will result in the following
 
508
                 *
 
509
                 * <div class="imgCrop_wrap">
 
510
                 *              <img ... this.img ... />
 
511
                 *              <div class="imgCrop_dragArea">
 
512
                 *                      <!-- the inner spans are only required for IE to stop it making the divs 1px high/wide -->
 
513
                 *                      <div class="imgCrop_overlay imageCrop_north"><span></span></div>
 
514
                 *                      <div class="imgCrop_overlay imageCrop_east"><span></span></div>
 
515
                 *                      <div class="imgCrop_overlay imageCrop_south"><span></span></div>
 
516
                 *                      <div class="imgCrop_overlay imageCrop_west"><span></span></div>
 
517
                 *                      <div class="imgCrop_selArea">
 
518
                 *                              <!-- marquees -->
 
519
                 *                              <!-- the inner spans are only required for IE to stop it making the divs 1px high/wide -->
 
520
                 *                              <div class="imgCrop_marqueeHoriz imgCrop_marqueeNorth"><span></span></div>
 
521
                 *                              <div class="imgCrop_marqueeVert imgCrop_marqueeEast"><span></span></div>
 
522
                 *                              <div class="imgCrop_marqueeHoriz imgCrop_marqueeSouth"><span></span></div>
 
523
                 *                              <div class="imgCrop_marqueeVert imgCrop_marqueeWest"><span></span></div>                        
 
524
                 *                              <!-- handles -->
 
525
                 *                              <div class="imgCrop_handle imgCrop_handleN"></div>
 
526
                 *                              <div class="imgCrop_handle imgCrop_handleNE"></div>
 
527
                 *                              <div class="imgCrop_handle imgCrop_handleE"></div>
 
528
                 *                              <div class="imgCrop_handle imgCrop_handleSE"></div>
 
529
                 *                              <div class="imgCrop_handle imgCrop_handleS"></div>
 
530
                 *                              <div class="imgCrop_handle imgCrop_handleSW"></div>
 
531
                 *                              <div class="imgCrop_handle imgCrop_handleW"></div>
 
532
                 *                              <div class="imgCrop_handle imgCrop_handleNW"></div>
 
533
                 *                              <div class="imgCrop_clickArea"></div>
 
534
                 *                      </div>  
 
535
                 *                      <div class="imgCrop_clickArea"></div>
 
536
                 *              </div>  
 
537
                 * </div>
 
538
                 */
 
539
 
 
540
                if (this.attached) {
 
541
                    if (this.monitorImage) {
 
542
                        this.setParams();
 
543
                    }
 
544
                    return;
 
545
                }
 
546
                 
 
547
                var cNamePrefix = 'imgCrop_';
 
548
                
 
549
                // get the point to insert the container
 
550
                var insertPoint = this.img.parentNode;
 
551
                
 
552
                // apply an extra class to the wrapper to fix Opera below version 9
 
553
                var fixOperaClass = '';
 
554
                if( this.isOpera8 ) fixOperaClass = ' opera8';
 
555
                this.imgWrap = Builder.node( 'div', { 'class': cNamePrefix + 'wrap' + fixOperaClass } );
 
556
                
 
557
                this.applyLinked = 0;
 
558
                this.saveLinked = 0;
 
559
                
 
560
                this.applyButton = Builder.node( 'div', { 'class': cNamePrefix + 'apply'} );
 
561
                if ((this.applyButton.parentNode)&&(this.options.tooltips)&&(this.options.tooltips.apply)) {
 
562
                    this.applyButton.setAttribute('title', this.options.tooltips.apply);
 
563
                }
 
564
                this.saveButton = Builder.node( 'div', { 'class': cNamePrefix + 'save'} );
 
565
                if ((this.saveButton.parentNode)&&(this.options.tooltips)&&(this.options.tooltips.save)) {
 
566
                    this.saveButton.setAttribute('title', this.options.tooltips.save);
 
567
                }
 
568
 
 
569
                
 
570
                this.north              = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'north' }, [Builder.node( 'span' )] );
 
571
                this.north_east         = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'north' }, [Builder.node( 'span' )] );
 
572
                this.north_west         = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'north' }, [Builder.node( 'span' )] );
 
573
                this.east               = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'east' } , [Builder.node( 'span' )] );
 
574
                this.south              = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'south' }, [Builder.node( 'span' )] );
 
575
                this.south_east         = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'south' }, [Builder.node( 'span' )] );
 
576
                this.south_west         = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'south' }, [Builder.node( 'span' )] );
 
577
                this.west               = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'west' } , [Builder.node( 'span' )] );
 
578
                
 
579
                var overlays    = [ this.north_west, this.north, this.north_east, this.east, this.south_east, this.south, this.south_west, this.west ];
 
580
 
 
581
                this.dragArea   = Builder.node( 'div', { 'class': cNamePrefix + 'dragArea' }, overlays );
 
582
                                                
 
583
                this.handleN    = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleN' } );
 
584
                this.handleNE   = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleNE' } );
 
585
                this.handleE    = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleE' } );
 
586
                this.handleSE   = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleSE' } );
 
587
                this.handleS    = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleS' } );
 
588
                this.handleSW   = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleSW' } );
 
589
                this.handleW    = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleW' } );
 
590
                this.handleNW   = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleNW' } );
 
591
                
 
592
                if (Prototype.Browser.IE) {
 
593
                    this.selArea        = Builder.node( 'div', { 'class': cNamePrefix + 'selArea' },
 
594
                        [
 
595
                                Builder.node( 'div', { 'class': cNamePrefix + 'marqueeHoriz ' + cNamePrefix + 'marqueeNorth' }, [Builder.node( 'span' )] ),
 
596
                                Builder.node( 'div', { 'class': cNamePrefix + 'marqueeVert ' + cNamePrefix + 'marqueeEast' }  , [Builder.node( 'span' )] ),
 
597
                                Builder.node( 'div', { 'class': cNamePrefix + 'marqueeHoriz ' + cNamePrefix + 'marqueeSouth' }, [Builder.node( 'span' )] ),
 
598
                                Builder.node( 'div', { 'class': cNamePrefix + 'marqueeVert ' + cNamePrefix + 'marqueeWest' }  , [Builder.node( 'span' )] ),
 
599
                                Builder.node( 'div', { 'class': cNamePrefix + 'clickArea' } )
 
600
                        ]
 
601
                    );
 
602
 
 
603
                    this.handleArea = Builder.node( 'div', { 'class': cNamePrefix + 'handleArea' },
 
604
                        [
 
605
                                this.handleN,
 
606
                                this.handleNE,
 
607
                                this.handleE,
 
608
                                this.handleSE,
 
609
                                this.handleS,
 
610
                                this.handleSW,
 
611
                                this.handleW,
 
612
                                this.handleNW
 
613
                        ]
 
614
                    );                          
 
615
 
 
616
                    this.dragArea.appendChild( this.handleArea );
 
617
                    
 
618
                    this.eventArea = this.handleArea;
 
619
                } else {
 
620
                    this.selArea        = Builder.node( 'div', { 'class': cNamePrefix + 'selArea' },
 
621
                        [
 
622
                                Builder.node( 'div', { 'class': cNamePrefix + 'marqueeHoriz ' + cNamePrefix + 'marqueeNorth' }, [Builder.node( 'span' )] ),
 
623
                                Builder.node( 'div', { 'class': cNamePrefix + 'marqueeVert ' + cNamePrefix + 'marqueeEast' }  , [Builder.node( 'span' )] ),
 
624
                                Builder.node( 'div', { 'class': cNamePrefix + 'marqueeHoriz ' + cNamePrefix + 'marqueeSouth' }, [Builder.node( 'span' )] ),
 
625
                                Builder.node( 'div', { 'class': cNamePrefix + 'marqueeVert ' + cNamePrefix + 'marqueeWest' }  , [Builder.node( 'span' )] ),
 
626
                                this.handleN,
 
627
                                this.handleNE,
 
628
                                this.handleE,
 
629
                                this.handleSE,
 
630
                                this.handleS,
 
631
                                this.handleSW,
 
632
                                this.handleW,
 
633
                                this.handleNW,
 
634
                                Builder.node( 'div', { 'class': cNamePrefix + 'clickArea' } )
 
635
                        ]
 
636
                    );
 
637
 
 
638
                    this.eventArea = this.selArea;
 
639
                }
 
640
                
 
641
                this.imgWrap.appendChild( this.img );
 
642
                this.imgWrap.appendChild( this.dragArea );
 
643
                this.dragArea.appendChild( this.selArea );
 
644
                this.dragArea.appendChild( Builder.node( 'div', { 'class': cNamePrefix + 'clickArea' } ) );
 
645
 
 
646
                insertPoint.appendChild( this.imgWrap );
 
647
 
 
648
                // add event observers
 
649
                this.startDragBind      = this.startDrag.bindAsEventListener( this );
 
650
                Event.observe( this.dragArea, 'mousedown', this.startDragBind );
 
651
                
 
652
                this.onDragBind         = this.onDrag.bindAsEventListener( this );
 
653
                Event.observe( document, 'mousemove', this.onDragBind );
 
654
                
 
655
                this.endCropBind        = this.endCrop.bindAsEventListener( this );
 
656
                Event.observe( document, 'mouseup', this.endCropBind );
 
657
//              Event.observe( this.imgWrap, 'mouseup', this.endCropBind );
 
658
                
 
659
                this.dblClickBind       = this.dblClick.bindAsEventListener( this );
 
660
                Event.observe( this.eventArea, 'dblclick', this.dblClickBind );
 
661
 
 
662
/*              if (Ext) {
 
663
                    Ext.EventManager.addListener(this.dragArea, "mousewheel", this.mouseScrollExt, this, {
 
664
                            stopEvent: true,
 
665
                            preventDefault: true
 
666
                    });
 
667
                } else {*/
 
668
                    this.mouseScrollBind        = this.mouseScroll.bindAsEventListener( this );
 
669
                    Event.observe( this.dragArea, 'DOMMouseScroll', this.mouseScrollBind );
 
670
                    Event.observe( this.dragArea, 'mousewheel', this.mouseScrollBind );
 
671
/*              }*/
 
672
                
 
673
                this.applyClickBind     = this.applyClick.bindAsEventListener( this );
 
674
                Event.observe( this.applyButton, 'mousedown', this.applyClickBind );
 
675
//              Event.observe( this.applyButton, 'click', this.applyClickBind );
 
676
 
 
677
                this.saveClickBind      = this.saveClick.bindAsEventListener( this );
 
678
                Event.observe( this.saveButton, 'mousedown', this.saveClickBind );
 
679
                
 
680
                this.resizeBind         = this.startResize.bindAsEventListener( this );
 
681
                this.handles = [ this.handleN, this.handleNE, this.handleE, this.handleSE, this.handleS, this.handleSW, this.handleW, this.handleNW ];
 
682
                this.registerHandles( true );
 
683
                
 
684
                if( this.options.captureKeys ) {
 
685
                        this.keysBind = this.handleKeys.bindAsEventListener( this );
 
686
                        Event.observe( document, 'keypress', this.keysBind );
 
687
                }
 
688
 
 
689
                // attach the dragable to the select area
 
690
                new CropDraggable( this.eventArea, { drawMethod: this.moveArea.bindAsEventListener( this ) } );
 
691
                
 
692
                this.setParams();
 
693
        },
 
694
        
 
695
        /**
 
696
         * Manages adding or removing the handle event handler and hiding or displaying them as appropriate
 
697
         * 
 
698
         * @access private
 
699
         * @param boolean registration true = add, false = remove
 
700
         * @return void
 
701
         */
 
702
        registerHandles: function( registration ) {     
 
703
                for( var i = 0; i < this.handles.length; i++ ) {
 
704
                        var handle = $( this.handles[i] );
 
705
                        
 
706
                        if( registration ) {
 
707
                                var hideHandle  = false;        // whether to hide the handle
 
708
                                
 
709
                                // disable handles asappropriate if we've got fixed dimensions
 
710
                                // if both dimensions are fixed we don't need to do much
 
711
                                if( this.fixedWidth && this.fixedHeight ) hideHandle = true;
 
712
                                else if( this.fixedWidth || this.fixedHeight ) {
 
713
                                        // if one of the dimensions is fixed then just hide those handles
 
714
                                        var isCornerHandle      = handle.className.match( /([S|N][E|W])$/ )
 
715
                                        var isWidthHandle       = handle.className.match( /(E|W)$/ );
 
716
                                        var isHeightHandle      = handle.className.match( /(N|S)$/ );
 
717
                                        if( isCornerHandle ) hideHandle = true;
 
718
                                        else if( this.fixedWidth && isWidthHandle ) hideHandle = true;
 
719
                                        else if( this.fixedHeight && isHeightHandle ) hideHandle = true;
 
720
                                }
 
721
                                if( hideHandle ) handle.hide();
 
722
                                else Event.observe( handle, 'mousedown', this.resizeBind );
 
723
                        } else {
 
724
                                handle.show();
 
725
                                Event.stopObserving( handle, 'mousedown', this.resizeBind );
 
726
                        }
 
727
                }
 
728
        },
 
729
                
 
730
        /**
 
731
         * Sets up all the cropper parameters, this can be used to reset the cropper when dynamically
 
732
         * changing the images
 
733
         * 
 
734
         * @access private
 
735
         * @return void
 
736
         */
 
737
        setParams: function() {
 
738
                /**
 
739
                 * @var int
 
740
                 * The image width
 
741
                 */
 
742
                this.imgW = this.img.width;
 
743
                /**
 
744
                 * @var int
 
745
                 * The image height
 
746
                 */
 
747
                this.imgH = this.img.height;
 
748
 
 
749
                $( this.north ).setStyle( { height: 0 } );
 
750
                $( this.north_east ).setStyle( { height: 0 } );
 
751
                $( this.north_west ).setStyle( { height: 0 } );
 
752
                $( this.east ).setStyle( { width: 0, height: 0 } );
 
753
                $( this.south ).setStyle( { height: 0 } );
 
754
                $( this.south_east ).setStyle( { height: 0 } );
 
755
                $( this.south_west ).setStyle( { height: 0 } );
 
756
                $( this.west ).setStyle( { width: 0, height: 0 } );
 
757
                
 
758
                // resize the container to fit the image
 
759
                $( this.imgWrap ).setStyle( { 'width': this.imgW + 'px', 'height': this.imgH + 'px' } );
 
760
                
 
761
                // hide the select area
 
762
                $( this.selArea ).hide();
 
763
                if (this.handleArea) $( this.handleArea).hide();
 
764
                
 
765
                // hide apply button
 
766
                if (this.applyLinked) {
 
767
                    this.dragArea.removeChild(this.applyButton);
 
768
                    this.applyLinked = false;
 
769
                }
 
770
 
 
771
                if (this.saveLinked) {
 
772
                    this.dragArea.removeChild(this.saveButton);
 
773
                    this.saveLinked = false;
 
774
                }
 
775
                
 
776
                this.selected = false;
 
777
                this.altered = false;
 
778
                                                
 
779
                // setup the starting position of the select area
 
780
                var startCoords = { x1: this.options.margins.left, y1: this.options.margins.top, x2: this.options.margins.left, y2: this.options.margins.top };
 
781
                var validCoordsSet = false;
 
782
                
 
783
                // display the select area 
 
784
                if( this.options.onloadCoords != null ) {
 
785
                        // if we've being given some coordinates to 
 
786
                        startCoords = this.cloneCoords( this.options.onloadCoords );
 
787
                        validCoordsSet = true;
 
788
                } else if( this.options.ratioDim.x > 0 && this.options.ratioDim.y > 0 ) {
 
789
                        // if there is a ratio limit applied and the then set it to initial ratio
 
790
                        startCoords.x1 = Math.ceil( ( this.imgW - this.options.ratioDim.x ) / 2 );
 
791
                        startCoords.y1 = Math.ceil( ( this.imgH - this.options.ratioDim.y ) / 2 );
 
792
                        startCoords.x2 = startCoords.x1 + this.options.ratioDim.x;
 
793
                        startCoords.y2 = startCoords.y1 + this.options.ratioDim.y;
 
794
                        validCoordsSet = true;
 
795
                }
 
796
                
 
797
                this.setAreaCoords( startCoords, false, false, 1 );
 
798
                
 
799
                if( this.options.displayOnInit && validCoordsSet ) {
 
800
                        this.selArea.show();
 
801
                        if (this.handleArea) this.handleArea.show();
 
802
                        this.drawArea();
 
803
                        this.endCrop();
 
804
                }
 
805
                
 
806
                this.attached = true;
 
807
        },
 
808
        
 
809
        /**
 
810
         * Removes the cropper
 
811
         * 
 
812
         * @access public
 
813
         * @return void
 
814
         */
 
815
        remove: function() {
 
816
                if( this.attached ) {
 
817
                        this.attached = false;
 
818
                        
 
819
                        // remove the elements we inserted
 
820
                        this.imgWrap.parentNode.insertBefore( this.img, this.imgWrap );
 
821
                        this.imgWrap.parentNode.removeChild( this.imgWrap );
 
822
                        
 
823
                        // remove the event observers
 
824
                        Event.stopObserving( this.dragArea, 'mousedown', this.startDragBind );
 
825
                        Event.stopObserving( document, 'mousemove', this.onDragBind );          
 
826
                        Event.stopObserving( document, 'mouseup', this.endCropBind );
 
827
//                      Event.stopObserving( this.imgWrap, 'mouseup', this.endCropBind );
 
828
                        Event.stopObserving( this.eventArea, 'dblclick', this.dblClickBind );
 
829
/*                      if (Ext) {
 
830
                            Ext.EventManager.removeListener(this.dragArea, "mousewheel", this.mouseScrollExt);
 
831
                        } else {*/
 
832
                            Event.stopObserving( this.dragArea, 'DOMMouseScroll', this.mouseScrollBind );
 
833
                            Event.stopObserving( this.dragArea, 'mousewheel', this.mouseScrollBind );
 
834
/*                      }*/
 
835
                        Event.stopObserving( this.applyButton, 'mousedown', this.applyClickBind );
 
836
                        Event.stopObserving( this.saveButton, 'mousedown', this.saveClickBind );
 
837
                        
 
838
                        this.registerHandles( false );
 
839
                        if( this.options.captureKeys ) Event.stopObserving( document, 'keypress', this.keysBind );
 
840
                }
 
841
        },
 
842
        
 
843
        /**
 
844
         * Resets the cropper, can be used either after being removed or any time you wish
 
845
         * 
 
846
         * @access public
 
847
         * @return void
 
848
         */
 
849
        reset: function() {
 
850
                if( !this.attached ) this.onLoad();
 
851
                else this.setParams();
 
852
                this.endCrop();
 
853
        },
 
854
        
 
855
        /**
 
856
         * Resets the cropper selection
 
857
         * 
 
858
         * @access public
 
859
         * @return void
 
860
         */
 
861
        clear: function() {
 
862
                $( this.north ).setStyle( { height: 0 } );
 
863
                $( this.north_east ).setStyle( { height: 0 } );
 
864
                $( this.north_west ).setStyle( { height: 0 } );
 
865
                $( this.east ).setStyle( { width: 0, height: 0 } );
 
866
                $( this.south ).setStyle( { height: 0 } );
 
867
                $( this.south_east ).setStyle( { height: 0 } );
 
868
                $( this.south_west ).setStyle( { height: 0 } );
 
869
                $( this.west ).setStyle( { width: 0, height: 0 } );
 
870
 
 
871
                // hide the select area
 
872
                $( this.selArea ).hide();
 
873
                if (this.handleArea) $( this.handleArea).hide();
 
874
                
 
875
                // hide apply button
 
876
                if (this.applyLinked) {
 
877
                    this.dragArea.removeChild(this.applyButton);
 
878
                    this.applyLinked = false;
 
879
                }
 
880
                if (this.saveLinked) {
 
881
                    this.dragArea.removeChild(this.saveButton);
 
882
                    this.saveLinked = false;
 
883
                }
 
884
                
 
885
                //
 
886
                this.selected = false;
 
887
                this.altered = false;
 
888
                
 
889
                this.setAreaCoords( { x1: this.options.margins.left, y1: this.options.margins.top, x2: this.options.margins.left, y2: this.options.margins.top }, false, false, null );
 
890
                this.drawArea();
 
891
        },
 
892
        
 
893
        /**
 
894
         * Handles the key functionality, currently just using arrow keys to move, if the user
 
895
         * presses shift then the area will move by 10 pixels
 
896
         */
 
897
        handleKeys: function( e ) {
 
898
                var dir = { x: 0, y: 0 }; // direction to move it in & the amount in pixels
 
899
                if( !this.dragging ) {
 
900
                        
 
901
                        // catch the arrow keys
 
902
                        switch( e.keyCode ) {
 
903
                                case( 37 ) : // left
 
904
                                        dir.x = -1;
 
905
                                        break;
 
906
                                case( 38 ) : // up
 
907
                                        dir.y = -1;
 
908
                                        break;
 
909
                                case( 39 ) : // right
 
910
                                        dir.x = 1;
 
911
                                        break
 
912
                                case( 40 ) : // down
 
913
                                        dir.y = 1;
 
914
                                        break;
 
915
                        }
 
916
                        
 
917
                        if( dir.x != 0 || dir.y != 0 ) {
 
918
                                // if shift is pressed then move by 10 pixels
 
919
                                if( e.shiftKey ) {
 
920
                                        dir.x *= 10;
 
921
                                        dir.y *= 10;
 
922
                                }
 
923
                                
 
924
                                this.moveArea( [ this.areaCoords.x1 + dir.x, this.areaCoords.y1 + dir.y ] );
 
925
                                Event.stop( e ); 
 
926
                        }
 
927
                }
 
928
        },
 
929
        
 
930
        /**
 
931
         * Calculates the width from the areaCoords
 
932
         * 
 
933
         * @access private
 
934
         * @return int
 
935
         */
 
936
        calcW: function() {
 
937
                return (this.areaCoords.x2 - this.areaCoords.x1)
 
938
        },
 
939
        
 
940
        /**
 
941
         * Calculates the height from the areaCoords
 
942
         * 
 
943
         * @access private
 
944
         * @return int
 
945
         */
 
946
        calcH: function() {
 
947
                return (this.areaCoords.y2 - this.areaCoords.y1)
 
948
        },
 
949
        
 
950
        /**
 
951
         * Moves the select area to the supplied point (assumes the point is x1 & y1 of the select area)
 
952
         * 
 
953
         * @access public
 
954
         * @param array Point for x1 & y1 to move select area to
 
955
         * @return void
 
956
         */
 
957
        moveArea: function( point ) {
 
958
                // dump( 'moveArea        : ' + point[0] + ',' + point[1] + ',' + ( point[0] + ( this.areaCoords.x2 - this.areaCoords.x1 ) ) + ',' + ( point[1] + ( this.areaCoords.y2 - this.areaCoords.y1 ) ) + '\n' );
 
959
                this.altered = true;
 
960
                this.setAreaCoords( 
 
961
                        {
 
962
                                x1: point[0], 
 
963
                                y1: point[1],
 
964
                                x2: point[0] + this.calcW(),
 
965
                                y2: point[1] + this.calcH()
 
966
                        },
 
967
                        true,
 
968
                        false
 
969
                );
 
970
                this.drawArea();
 
971
        },
 
972
 
 
973
        /**
 
974
         * Clones a co-ordinates object, stops problems with handling them by reference
 
975
         * 
 
976
         * @access private
 
977
         * @param obj Coordinate object x1, y1, x2, y2
 
978
         * @return obj Coordinate object x1, y1, x2, y2
 
979
         */
 
980
        cloneCoords: function( coords ) {
 
981
                return { x1: coords.x1, y1: coords.y1, x2: coords.x2, y2: coords.y2 };
 
982
        },
 
983
 
 
984
        /**
 
985
         * Sets the select coords to those provided but ensures they don't go
 
986
         * outside the bounding box
 
987
         * 
 
988
         * @access private
 
989
         * @param obj Coordinates x1, y1, x2, y2
 
990
         * @param boolean Whether this is a move
 
991
         * @param boolean Whether to apply squaring
 
992
         * @param obj Direction of mouse along both axis x, y ( -1 = negative, 1 = positive ) only required when moving etc.
 
993
         * @param string The current resize handle || null
 
994
         * @return void
 
995
         */
 
996
        setAreaCoords: function( coords, moving, square, direction, resizeHandle ) {
 
997
                // dump( 'setAreaCoords (in) : ' + coords.x1 + ',' + coords.y1 + ',' + coords.x2 + ',' + coords.y2 );
 
998
                if( moving ) {
 
999
                        // if moving
 
1000
                        var targW = coords.x2 - coords.x1;
 
1001
                        var targH = coords.y2 - coords.y1;
 
1002
                        
 
1003
                        // ensure we're within the bounds
 
1004
                        if( coords.x1 < this.options.margins.left ) {
 
1005
                                coords.x1 = this.options.margins.left;
 
1006
                                coords.x2 = this.options.margins.left + targW;
 
1007
                        }
 
1008
                        if( coords.y1 < this.options.margins.top ) {
 
1009
                                coords.y1 = this.options.margins.top;
 
1010
                                coords.y2 = this.options.margins.top + targH;
 
1011
                        }
 
1012
                        if( coords.x2 > (this.imgW - this.options.margins.right) ) {
 
1013
                                coords.x2 = this.imgW - this.options.margins.right;
 
1014
                                coords.x1 = coords.x2 - targW;
 
1015
                        }
 
1016
                        if( coords.y2 > (this.imgH - this.options.margins.bottom) ) {
 
1017
                                coords.y2 = this.imgH - this.options.margins.bottom;
 
1018
                                coords.y1 = coords.y2 - targH;
 
1019
                        }                       
 
1020
                } else {
 
1021
                        // ensure we're within the bounds
 
1022
                        if( coords.x1 < this.options.margins.left ) coords.x1 = this.options.margins.left;
 
1023
                        if( coords.y1 < this.options.margins.top ) coords.y1 = this.options.margins.top;
 
1024
                        if( coords.x2 > (this.imgW - this.options.margins.right) ) coords.x2 = this.imgW - this.options.margins.right;
 
1025
                        if( coords.y2 > (this.imgH - this.options.margins.bottom) ) coords.y2 = this.imgH - this.options.margins.bottom;
 
1026
                        
 
1027
                        // This is passed as null in onload
 
1028
                        if( direction != null ) {
 
1029
                                                                
 
1030
                                // apply the ratio or squaring where appropriate
 
1031
                                if( this.ratioX > 0 ) this.applyRatio( coords, { x: this.ratioX, y: this.ratioY }, direction, resizeHandle );
 
1032
                                else if( square ) this.applyRatio( coords, { x: 1, y: 1 }, direction, resizeHandle );
 
1033
                                                                                
 
1034
                                var mins = [ this.options.minWidth, this.options.minHeight ]; // minimum dimensions [x,y]                       
 
1035
                                var maxs = [ this.options.maxWidth, this.options.maxHeight ]; // maximum dimensions [x,y]
 
1036
                
 
1037
                                // apply dimensions where appropriate
 
1038
                                if( mins[0] > 0 || mins[1] > 0 || maxs[0] > 0 || maxs[1] > 0) {
 
1039
                                
 
1040
                                        var coordsTransX        = { a1: coords.x1, a2: coords.x2 };
 
1041
                                        var coordsTransY        = { a1: coords.y1, a2: coords.y2 };
 
1042
                                        var boundsX                     = { min: 0, max: this.imgW };
 
1043
                                        var boundsY                     = { min: 0, max: this.imgH };
 
1044
                                        
 
1045
                                        // handle squaring properly on single axis minimum dimensions
 
1046
                                        if( (mins[0] != 0 || mins[1] != 0) && square ) {
 
1047
                                                if( mins[0] > 0 ) mins[1] = mins[0];
 
1048
                                                else if( mins[1] > 0 ) mins[0] = mins[1];
 
1049
                                        }
 
1050
                                        
 
1051
                                        if( (maxs[0] != 0 || maxs[0] != 0) && square ) {
 
1052
                                                // if we have a max x value & it is less than the max y value then we set the y max to the max x (so we don't go over the minimum maximum of one of the axes - if that makes sense)
 
1053
                                                if( maxs[0] > 0 && maxs[0] <= maxs[1] ) maxs[1] = maxs[0];
 
1054
                                                else if( maxs[1] > 0 && maxs[1] <= maxs[0] ) maxs[0] = maxs[1];
 
1055
                                        }
 
1056
                                        
 
1057
                                        if( mins[0] > 0 ) this.applyDimRestriction( coordsTransX, mins[0], direction.x, boundsX, 'min' );
 
1058
                                        if( mins[1] > 1 ) this.applyDimRestriction( coordsTransY, mins[1], direction.y, boundsY, 'min' );
 
1059
                                        
 
1060
                                        if( maxs[0] > 0 ) this.applyDimRestriction( coordsTransX, maxs[0], direction.x, boundsX, 'max' );
 
1061
                                        if( maxs[1] > 1 ) this.applyDimRestriction( coordsTransY, maxs[1], direction.y, boundsY, 'max' );
 
1062
                                        
 
1063
                                        coords = { x1: coordsTransX.a1, y1: coordsTransY.a1, x2: coordsTransX.a2, y2: coordsTransY.a2 };
 
1064
                                }
 
1065
                                
 
1066
                        }
 
1067
                }
 
1068
                
 
1069
//              dump( 'setAreaCoords (out) : ' + coords.x1 + ',' + coords.y1 + ',' + coords.x2 + ',' + coords.y2 + '\n' );
 
1070
                this.areaCoords = coords;
 
1071
        },
 
1072
        
 
1073
        /**
 
1074
         * Applies the supplied dimension restriction to the supplied coordinates along a single axis
 
1075
         * 
 
1076
         * @access private
 
1077
         * @param obj Single axis coordinates, a1, a2 (e.g. for the x axis a1 = x1 & a2 = x2)
 
1078
         * @param int The restriction value
 
1079
         * @param int The direction ( -1 = negative, 1 = positive )
 
1080
         * @param obj The bounds of the image ( for this axis )
 
1081
         * @param string The dimension restriction type ( 'min' | 'max' )
 
1082
         * @return void
 
1083
         */
 
1084
        applyDimRestriction: function( coords, val, direction, bounds, type ) {
 
1085
                var check;
 
1086
                if( type == 'min' ) check = ( ( coords.a2 - coords.a1 ) < val );
 
1087
                else check = ( ( coords.a2 - coords.a1 ) > val );
 
1088
                if( check ) {
 
1089
                        if( direction == 1 ) coords.a2 = coords.a1 + val;
 
1090
                        else coords.a1 = coords.a2 - val;
 
1091
                        
 
1092
                        // make sure we're still in the bounds (not too pretty for the user, but needed)
 
1093
                        if( coords.a1 < bounds.min ) {
 
1094
                                coords.a1 = bounds.min;
 
1095
                                coords.a2 = val;
 
1096
                        } else if( coords.a2 > bounds.max ) {
 
1097
                                coords.a1 = bounds.max - val;
 
1098
                                coords.a2 = bounds.max;
 
1099
                        }
 
1100
                }
 
1101
        },
 
1102
        
 
1103
        /**
 
1104
         * Applies the supplied ratio to the supplied coordinates
 
1105
         * 
 
1106
         * @access private
 
1107
         * @param obj Coordinates, x1, y1, x2, y2
 
1108
         * @param obj Ratio, x, y
 
1109
         * @param obj Direction of mouse, x & y : -1 == negative 1 == positive
 
1110
         * @param string The current resize handle || null
 
1111
         * @return void
 
1112
         */
 
1113
        applyRatio : function( coords, ratio, direction, resizeHandle ) {
 
1114
                // dump( 'direction.y : ' + direction.y + '\n');
 
1115
                var newCoords;
 
1116
                if( resizeHandle == 'N' || resizeHandle == 'S' ) {
 
1117
                        // dump( 'north south \n');
 
1118
                        // if moving on either the lone north & south handles apply the ratio on the y axis
 
1119
                        newCoords = this.applyRatioToAxis( 
 
1120
                                { a1: coords.y1, b1: coords.x1, a2: coords.y2, b2: coords.x2 },
 
1121
                                { a: ratio.y, b: ratio.x },
 
1122
                                { a: direction.y, b: direction.x },
 
1123
                                { min: 0, max: this.imgW }
 
1124
                        );
 
1125
                        coords.x1 = newCoords.b1;
 
1126
                        coords.y1 = newCoords.a1;
 
1127
                        coords.x2 = newCoords.b2;
 
1128
                        coords.y2 = newCoords.a2;
 
1129
                } else {
 
1130
                        // otherwise deal with it as if we're applying the ratio on the x axis
 
1131
                        newCoords = this.applyRatioToAxis( 
 
1132
                                { a1: coords.x1, b1: coords.y1, a2: coords.x2, b2: coords.y2 },
 
1133
                                { a: ratio.x, b: ratio.y },
 
1134
                                { a: direction.x, b: direction.y },
 
1135
                                { min: 0, max: this.imgH }
 
1136
                        );
 
1137
                        coords.x1 = newCoords.a1;
 
1138
                        coords.y1 = newCoords.b1;
 
1139
                        coords.x2 = newCoords.a2;
 
1140
                        coords.y2 = newCoords.b2;
 
1141
                }
 
1142
                
 
1143
        },
 
1144
        
 
1145
        /**
 
1146
         * Applies the provided ratio to the provided coordinates based on provided direction & bounds,
 
1147
         * use to encapsulate functionality to make it easy to apply to either axis. This is probably
 
1148
         * quite hard to visualise so see the x axis example within applyRatio()
 
1149
         * 
 
1150
         * Example in parameter details & comments is for requesting applying ratio to x axis.
 
1151
         * 
 
1152
         * @access private
 
1153
         * @param obj Coords object (a1, b1, a2, b2) where a = x & b = y in example
 
1154
         * @param obj Ratio object (a, b) where a = x & b = y in example
 
1155
         * @param obj Direction object (a, b) where a = x & b = y in example
 
1156
         * @param obj Bounds (min, max)
 
1157
         * @return obj Coords object (a1, b1, a2, b2) where a = x & b = y in example
 
1158
         */
 
1159
        applyRatioToAxis: function( coords, ratio, direction, bounds ) {
 
1160
                var newCoords = Object.extend( coords, {} );
 
1161
                var calcDimA = newCoords.a2 - newCoords.a1;                     // calculate dimension a (e.g. width)
 
1162
                var targDimB = Math.floor( calcDimA * ratio.b / ratio.a );      // the target dimension b (e.g. height)
 
1163
                var targB;                                                                                      // to hold target b (e.g. y value)
 
1164
                var targDimA;                                           // to hold target dimension a (e.g. width)
 
1165
                var calcDimB = null;                                                            // to hold calculated dimension b (e.g. height)
 
1166
                
 
1167
                // dump( 'newCoords[0]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');
 
1168
                                
 
1169
                if( direction.b == 1 ) {                                                        // if travelling in a positive direction
 
1170
                        // make sure we're not going out of bounds
 
1171
                        targB = newCoords.b1 + targDimB;
 
1172
                        if( targB > bounds.max ) {
 
1173
                                targB = bounds.max;
 
1174
                                calcDimB = targB - newCoords.b1;                        // calcuate dimension b (e.g. height)
 
1175
                        }
 
1176
                        
 
1177
                        newCoords.b2 = targB;
 
1178
                } else {                                                                                        // if travelling in a negative direction
 
1179
                        // make sure we're not going out of bounds
 
1180
                        targB = newCoords.b2 - targDimB;
 
1181
                        if( targB < bounds.min ) {
 
1182
                                targB = bounds.min;
 
1183
                                calcDimB = targB + newCoords.b2;                        // calcuate dimension b (e.g. height)
 
1184
                        }
 
1185
                        newCoords.b1 = targB;
 
1186
                }
 
1187
                
 
1188
                // dump( 'newCoords[1]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');
 
1189
                        
 
1190
                // apply the calculated dimensions
 
1191
                if( calcDimB != null ) {
 
1192
                        targDimA = Math.floor( calcDimB * ratio.a / ratio.b );
 
1193
                        
 
1194
                        if( direction.a == 1 ) newCoords.a2 = newCoords.a1 + targDimA;
 
1195
                        else newCoords.a1 = newCoords.a1 = newCoords.a2 - targDimA;
 
1196
                }
 
1197
                
 
1198
                // dump( 'newCoords[2]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');
 
1199
                        
 
1200
                return newCoords;
 
1201
        },
 
1202
        
 
1203
        /**
 
1204
         * Draws the select area
 
1205
         * 
 
1206
         * @access private
 
1207
         * @return void
 
1208
         */
 
1209
        drawArea: function( ) { 
 
1210
                /*
 
1211
                 * NOTE: I'm not using the Element.setStyle() shortcut as they make it 
 
1212
                 * quite sluggish on Mac based browsers
 
1213
                 */
 
1214
                // dump( 'drawArea        : ' + this.areaCoords.x1 + ',' + this.areaCoords.y1 + ',' + this.areaCoords.x2 + ',' + this.areaCoords.y2 + '\n' );
 
1215
                var areaWidth     = this.calcW();
 
1216
                var areaHeight    = this.calcH();
 
1217
                
 
1218
                /*
 
1219
                 * Calculate all the style strings before we use them, allows reuse & produces quicker
 
1220
                 * rendering (especially noticable in Mac based browsers)
 
1221
                 */
 
1222
                var px = 'px';
 
1223
                var params = [
 
1224
                        this.areaCoords.x1 + px,        // the left of the selArea
 
1225
                        this.areaCoords.y1 + px,                // the top of the selArea
 
1226
                        areaWidth + px,                                 // width of the selArea
 
1227
                        areaHeight + px,                                        // height of the selArea
 
1228
                        this.areaCoords.x2 + px,                // bottom of the selArea
 
1229
                        this.areaCoords.y2 + px,                // right of the selArea
 
1230
                        (this.img.width - this.areaCoords.x2) + px,     // right edge of selArea
 
1231
                        (this.img.height - this.areaCoords.y2) + px     // bottom edge of selArea
 
1232
                ];
 
1233
                                
 
1234
                // do the select area
 
1235
                var areaStyle                           = this.selArea.style;
 
1236
                areaStyle.left                          = params[0];
 
1237
                areaStyle.top                           = params[1];
 
1238
                areaStyle.width                         = params[2];
 
1239
                areaStyle.height                        = params[3];
 
1240
 
 
1241
                if (this.handleArea) {          
 
1242
                    var handleStyle                     = this.handleArea.style;
 
1243
                    handleStyle.left                    = params[0];
 
1244
                    handleStyle.top                     = params[1];
 
1245
                    handleStyle.width                   = params[2];
 
1246
                    handleStyle.height                  = params[3];
 
1247
                }
 
1248
                                
 
1249
                // position the north, east, south & west handles
 
1250
                var horizHandlePos = Math.ceil( (areaWidth - 6) / 2 ) + px;
 
1251
                var vertHandlePos = Math.ceil( (areaHeight - 6) / 2 ) + px;
 
1252
                
 
1253
                this.handleN.style.left         = horizHandlePos;
 
1254
                this.handleE.style.top          = vertHandlePos;
 
1255
                this.handleS.style.left         = horizHandlePos;
 
1256
                this.handleW.style.top          = vertHandlePos;
 
1257
                
 
1258
                if ((areaWidth < this.options.allWidth)&&(areaHeight<this.options.allHeight)) {
 
1259
                    if (this.applyLinked) {
 
1260
                        this.dragArea.removeChild(this.applyButton);
 
1261
                        this.applyLinked = false;
 
1262
                    }
 
1263
                    if (this.saveLinked) {
 
1264
                        this.dragArea.removeChild(this.saveButton);
 
1265
                        this.saveLinked = false;
 
1266
                    }
 
1267
                    
 
1268
                    this.east.className = "imgCrop_overlay";
 
1269
                    this.west.className = "imgCrop_overlay";
 
1270
                    this.north.className = "imgCrop_overlay";
 
1271
                    this.south.className = "imgCrop_overlay";
 
1272
                } else if (areaWidth < this.options.allWidth) {
 
1273
                    if ((this.options.onApplyClick)&&(!this.applyLinked)) {
 
1274
                        this.dragArea.appendChild(this.applyButton);
 
1275
                        this.applyLinked = true;
 
1276
                    }
 
1277
                    if ((this.options.onSaveClick)&&(!this.saveLinked)) {
 
1278
                        this.dragArea.appendChild(this.saveButton);
 
1279
                        this.saveLinked = true;
 
1280
                    }
 
1281
                    
 
1282
                    this.east.className = "imgCrop_selArea";
 
1283
                    this.west.className = "imgCrop_selArea";
 
1284
                    this.north.className = "imgCrop_overlay";
 
1285
                    this.south.className = "imgCrop_overlay";
 
1286
                } else if (areaHeight < this.options.allHeight) {
 
1287
                    if ((this.options.onApplyClick)&&(!this.applyLinked)) {
 
1288
                        this.dragArea.appendChild(this.applyButton);
 
1289
                        this.applyLinked = true;
 
1290
                    }
 
1291
                    if ((this.options.onSaveClick)&&(!this.saveLinked)) {
 
1292
                        this.dragArea.appendChild(this.saveButton);
 
1293
                        this.saveLinked = true;
 
1294
                    }
 
1295
                    
 
1296
                    this.east.className = "imgCrop_overlay";
 
1297
                    this.west.className = "imgCrop_overlay";
 
1298
                    this.north.className = "imgCrop_selArea";
 
1299
                    this.south.className = "imgCrop_selArea";
 
1300
                } else {
 
1301
                    if ((this.options.onApplyClick)&&(!this.applyLinked)) {
 
1302
                        this.dragArea.appendChild(this.applyButton);
 
1303
                        this.applyLinked = true;
 
1304
                    }
 
1305
                    if ((this.options.onSaveClick)&&(!this.saveLinked)) {
 
1306
                        this.dragArea.appendChild(this.saveButton);
 
1307
                        this.saveLinked = true;
 
1308
                    }
 
1309
                    
 
1310
                    this.east.className = "imgCrop_overlay";
 
1311
                    this.west.className = "imgCrop_overlay";
 
1312
                    this.north.className = "imgCrop_overlay";
 
1313
                    this.south.className = "imgCrop_overlay";
 
1314
                }
 
1315
                
 
1316
                // draw the four overlays
 
1317
                var northStyle                  = this.north.style;
 
1318
                northStyle.height               = params[1];
 
1319
                northStyle.left                 = params[0];
 
1320
                northStyle.width                = params[2];
 
1321
                
 
1322
                var neStyle                     = this.north_east.style;
 
1323
                neStyle.height                  = params[1];
 
1324
                neStyle.width                   = params[0];
 
1325
 
 
1326
                var nwStyle                     = this.north_west.style;
 
1327
                nwStyle.height                  = params[1];
 
1328
                nwStyle.left                    = params[4];
 
1329
                nwStyle.width                   = params[6];
 
1330
                
 
1331
                var eastStyle                   = this.east.style;
 
1332
                eastStyle.top                   = params[1];
 
1333
                eastStyle.height                = params[3];
 
1334
                eastStyle.left                  = params[4];
 
1335
                eastStyle.width                 = params[6];
 
1336
           
 
1337
                var southStyle                  = this.south.style;
 
1338
                southStyle.top                  = params[5];
 
1339
                southStyle.height               = params[7];
 
1340
                southStyle.left                 = params[0];
 
1341
                southStyle.width                = params[2];
 
1342
 
 
1343
                var seStyle                     = this.south_east.style;
 
1344
                seStyle.top                     = params[5];
 
1345
                seStyle.height                  = params[7];
 
1346
                seStyle.width                   = params[0];
 
1347
 
 
1348
                var swStyle                     = this.south_west.style;
 
1349
                swStyle.top                     = params[5];
 
1350
                swStyle.height                  = params[7];
 
1351
                swStyle.left                    = params[4];
 
1352
                swStyle.width                   = params[6];
 
1353
           
 
1354
                var westStyle                   = this.west.style;
 
1355
                westStyle.top                   = params[1];
 
1356
                westStyle.height                = params[3];
 
1357
                westStyle.width                 = params[0];
 
1358
                
 
1359
                var buttonWidth = 0;
 
1360
                
 
1361
                if (this.applyButton.parentNode) {
 
1362
                    var applyStyle = this.applyButton.style;
 
1363
                    applyStyle.left = params[4];
 
1364
                    applyStyle.top = params[5];
 
1365
 
 
1366
                    buttonWidth += 1 + this.applyButton.offsetWidth;
 
1367
                }
 
1368
 
 
1369
                if (this.saveButton.parentNode) {
 
1370
                    var saveStyle = this.saveButton.style;
 
1371
                    saveStyle.left = (this.areaCoords.x2 - buttonWidth) + px;
 
1372
                    saveStyle.top = params[5];
 
1373
                }
 
1374
                
 
1375
                // call the draw method on sub classes
 
1376
                this.subDrawArea();
 
1377
                
 
1378
                this.forceReRender();
 
1379
        },
 
1380
        
 
1381
        /**
 
1382
         * Force the re-rendering of the selArea element which fixes rendering issues in Safari 
 
1383
         * & IE PC, especially evident when re-sizing perfectly vertical using any of the south handles
 
1384
         * 
 
1385
         * @access private
 
1386
         * @return void
 
1387
         */
 
1388
        forceReRender: function() {
 
1389
                if( this.isIE || this.isWebKit) {
 
1390
                        var n = document.createTextNode(' ');
 
1391
                        var d,el,fixEL,i;
 
1392
                
 
1393
                        if( this.isIE ) fixEl = this.selArea;
 
1394
                        else if( this.isWebKit ) {
 
1395
                                fixEl = document.getElementsByClassName( 'imgCrop_marqueeSouth', this.imgWrap )[0];
 
1396
                                /* we have to be a bit more forceful for Safari, otherwise the the marquee &
 
1397
                                 * the south handles still don't move
 
1398
                                 */ 
 
1399
                                d = Builder.node( 'div', '' );
 
1400
                                d.style.visibility = 'hidden';
 
1401
                                
 
1402
                                var classList = ['SE','S','SW'];
 
1403
                                for( i = 0; i < classList.length; i++ ) {
 
1404
                                        el = document.getElementsByClassName( 'imgCrop_handle' + classList[i], this.selArea )[0];
 
1405
                                        if( el.childNodes.length ) el.removeChild( el.childNodes[0] );
 
1406
                                        el.appendChild(d);
 
1407
                                }
 
1408
                        }
 
1409
                        fixEl.appendChild(n);
 
1410
                        fixEl.removeChild(n);
 
1411
                }
 
1412
        },
 
1413
        
 
1414
        /**
 
1415
         * Starts the resize
 
1416
         * 
 
1417
         * @access private
 
1418
         * @param obj Event
 
1419
         * @return void
 
1420
         */
 
1421
        startResize: function( e ) {
 
1422
                this.startCoords = this.cloneCoords( this.areaCoords );
 
1423
                
 
1424
                this.altered = true;
 
1425
                this.resizing = true;
 
1426
 
 
1427
                this.resizeHandle = Event.element( e ).classNames().toString().replace(/([^N|NE|E|SE|S|SW|W|NW])+/, '');
 
1428
                // dump( 'this.resizeHandle : ' + this.resizeHandle + '\n' );
 
1429
                Event.stop( e );
 
1430
        },
 
1431
        
 
1432
        /**
 
1433
         * Starts the drag
 
1434
         * 
 
1435
         * @access private
 
1436
         * @param obj Event
 
1437
         * @return void
 
1438
         */
 
1439
        startDrag: function( e ) {      
 
1440
            if (!this.dragging) {
 
1441
                var curtime = new Date();
 
1442
                this.start_time = curtime.getTime();
 
1443
            }
 
1444
                
 
1445
            this.selArea.show();
 
1446
            if (this.handleArea) this.handleArea.show();
 
1447
            this.clickCoords = this.getCurPos( e );
 
1448
 
 
1449
            this.setAreaCoords( { x1: this.clickCoords.x, y1: this.clickCoords.y, x2: this.clickCoords.x, y2: this.clickCoords.y }, false, false, null );
 
1450
 
 
1451
            this.altered = true;
 
1452
            this.dragging = true;
 
1453
 
 
1454
            if (this.options.onClick)
 
1455
                this.dragged = false;
 
1456
            else
 
1457
                this.onDrag( e ); // incase the user just clicks once after already making a selection
 
1458
            Event.stop( e );
 
1459
        },
 
1460
        
 
1461
        /**
 
1462
         * Gets the current cursor position relative to the image
 
1463
         * 
 
1464
         * @access private
 
1465
         * @param obj Event
 
1466
         * @return obj x,y pixels of the cursor
 
1467
         */
 
1468
        getCurPos: function( e ) {
 
1469
                // get the offsets for the wrapper within the document
 
1470
                var el = this.imgWrap, wrapOffsets = Position.cumulativeOffset( el );
 
1471
                
 
1472
                // remove any scrolling that is applied to the wrapper (this may be buggy) - don't count the scroll on the body as that won't affect us
 
1473
/* DS: Causes problems in Opera9 (IE, Seamonkey are OK)
 
1474
                while( el.nodeName != 'BODY' ) {
 
1475
                        wrapOffsets[1] -= el.scrollTop  || 0;
 
1476
                        wrapOffsets[0] -= el.scrollLeft || 0;
 
1477
                        el = el.parentNode;
 
1478
                }
 
1479
*/
 
1480
                return curPos = { 
 
1481
                        x: Event.pointerX(e) - wrapOffsets[0],
 
1482
                        y: Event.pointerY(e) - wrapOffsets[1]
 
1483
                }
 
1484
        },
 
1485
        
 
1486
        /**
 
1487
         * Performs the drag for both resize & inital draw dragging
 
1488
         * 
 
1489
         * @access private
 
1490
         * @param obj Event
 
1491
         * @return void
 
1492
         */
 
1493
        onDrag: function( e ) {
 
1494
                if( this.dragging || this.resizing ) {  
 
1495
                        this.dragged = true;
 
1496
                
 
1497
                        var resizeHandle = null;
 
1498
                        var curPos = this.getCurPos( e );                       
 
1499
                        var newCoords = this.cloneCoords( this.areaCoords );
 
1500
                        var direction = { x: 1, y: 1 };
 
1501
                                                
 
1502
                    if( this.dragging ) {
 
1503
                        if( curPos.x < this.clickCoords.x ) direction.x = -1;
 
1504
                        if( curPos.y < this.clickCoords.y ) direction.y = -1;
 
1505
                        
 
1506
                                this.transformCoords( curPos.x, this.clickCoords.x, newCoords, 'x' );
 
1507
                                this.transformCoords( curPos.y, this.clickCoords.y, newCoords, 'y' );
 
1508
                    } else if( this.resizing ) {
 
1509
                                resizeHandle = this.resizeHandle;                       
 
1510
                                // do x movements first
 
1511
                                if( resizeHandle.match(/E/) ) {
 
1512
                                        // if we're moving an east handle
 
1513
                                        this.transformCoords( curPos.x, this.startCoords.x1, newCoords, 'x' );  
 
1514
                                        if( curPos.x < this.startCoords.x1 ) direction.x = -1;
 
1515
                                } else if( resizeHandle.match(/W/) ) {
 
1516
                                        // if we're moving an west handle
 
1517
                                        this.transformCoords( curPos.x, this.startCoords.x2, newCoords, 'x' );
 
1518
                                        if( curPos.x < this.startCoords.x2 ) direction.x = -1;
 
1519
                                }
 
1520
                                                                        
 
1521
                                // do y movements second
 
1522
                                if( resizeHandle.match(/N/) ) {
 
1523
                                        // if we're moving an north handle      
 
1524
                                        this.transformCoords( curPos.y, this.startCoords.y2, newCoords, 'y' );
 
1525
                                        if( curPos.y < this.startCoords.y2 ) direction.y = -1;
 
1526
                                } else if( resizeHandle.match(/S/) ) {
 
1527
                                        // if we're moving an south handle
 
1528
                                        this.transformCoords( curPos.y, this.startCoords.y1, newCoords, 'y' );  
 
1529
                                        if( curPos.y < this.startCoords.y1 ) direction.y = -1;
 
1530
                                }       
 
1531
                                                        
 
1532
                        }
 
1533
                
 
1534
                        this.setAreaCoords( newCoords, false, e.shiftKey, direction, resizeHandle );
 
1535
                        this.drawArea();
 
1536
                        Event.stop( e ); // stop the default event (selecting images & text) in Safari & IE PC
 
1537
                }
 
1538
        },
 
1539
        
 
1540
        /**
 
1541
         * Applies the appropriate transform to supplied co-ordinates, on the
 
1542
         * defined axis, depending on the relationship of the supplied values
 
1543
         * 
 
1544
         * @access private
 
1545
         * @param int Current value of pointer
 
1546
         * @param int Base value to compare current pointer val to
 
1547
         * @param obj Coordinates to apply transformation on x1, x2, y1, y2
 
1548
         * @param string Axis to apply transformation on 'x' || 'y'
 
1549
         * @return void
 
1550
         */
 
1551
        transformCoords : function( curVal, baseVal, coords, axis ) {
 
1552
                var newVals = [ curVal, baseVal ];
 
1553
                if( curVal > baseVal ) newVals.reverse();
 
1554
                coords[ axis + '1' ] = newVals[0];
 
1555
                coords[ axis + '2' ] = newVals[1];              
 
1556
        },
 
1557
        
 
1558
        /**
 
1559
         * Ends the crop & passes the values of the select area on to the appropriate 
 
1560
         * callback function on completion of a crop
 
1561
         * 
 
1562
         * @access private
 
1563
         * @return void
 
1564
         */
 
1565
        endCrop : function(e) {
 
1566
                var click = false;
 
1567
                var w, h;
 
1568
                
 
1569
                if ((this.dragging)&&(this.start_time)) {
 
1570
                    var curtime = new Date();
 
1571
                    var duration = curtime.getTime() - this.start_time;
 
1572
                    
 
1573
                    if (duration < this.options.clickDuration) click = true;
 
1574
                } 
 
1575
 
 
1576
                
 
1577
                this.dragging = false;
 
1578
                this.resizing = false;
 
1579
                
 
1580
                w = this.calcW();
 
1581
                h = this.calcH();
 
1582
                
 
1583
                if (click) {
 
1584
                    if ((w>this.options.allWidth)||(h>this.options.allHeight)) click = false;
 
1585
                }
 
1586
                
 
1587
                if (click) {
 
1588
                    var x = this.areaCoords.x1;
 
1589
                    var y = this.areaCoords.y1;
 
1590
 
 
1591
                    if (this.selected) {
 
1592
                        this.options.onCancelCrop();
 
1593
                        this.clear();
 
1594
                    } else if (this.options.onClick) {
 
1595
                        this.options.onClick(e,
 
1596
                            {
 
1597
                                x: x,
 
1598
                                y: y
 
1599
                            }
 
1600
                        );
 
1601
                        this.clear();
 
1602
                    }
 
1603
                } else if ((w>this.options.allWidth)||(h>this.options.allHeight)) {
 
1604
                    if (!this.dragged)  this.onDrag( e );
 
1605
                    
 
1606
                    if (this.altered) {
 
1607
                        this.options.onEndCrop(
 
1608
                            this.areaCoords,
 
1609
                            {
 
1610
                                width: w, 
 
1611
                                height: h 
 
1612
                            }
 
1613
                        );
 
1614
                        this.altered = false;
 
1615
                    }
 
1616
                    this.selected = true;
 
1617
                } else {
 
1618
                    this.clear();
 
1619
                }
 
1620
        },
 
1621
        
 
1622
        /**
 
1623
         * Passes the values of the select area on to the appropriate 
 
1624
         * callback function on double click in cropping area
 
1625
         * 
 
1626
         * @access private
 
1627
         * @return void
 
1628
         */
 
1629
        dblClick : function() {
 
1630
                this.dragging = false;
 
1631
                this.resizing = false;
 
1632
                
 
1633
                var w = this.calcW();
 
1634
                var h = this.calcH();
 
1635
                
 
1636
                if ((w > this.options.allWidth)&&(h > this.options.allHeight)) {
 
1637
                    this.options.onDblClick(
 
1638
                        this.areaCoords,
 
1639
                        {
 
1640
                                width: w, 
 
1641
                                height: h 
 
1642
                        }
 
1643
                    );
 
1644
                }
 
1645
        },
 
1646
 
 
1647
        mouseScroll : function(ev) {
 
1648
            if (this.options.onMouseScroll) {
 
1649
                this.dragging = false;
 
1650
                this.resizing = false;
 
1651
                
 
1652
                var delta = domGetScrollEventDelta(ev);
 
1653
                
 
1654
                this.options.onMouseScroll(delta,
 
1655
                        this.areaCoords
 
1656
                );
 
1657
            }
 
1658
 
 
1659
        },
 
1660
/*
 
1661
        mouseScrollExt: function(e) {
 
1662
                this.options.onMouseScroll(-e.getWheelDelta(),
 
1663
                        this.areaCoords
 
1664
                );
 
1665
        },
 
1666
*/
 
1667
 
 
1668
        /**
 
1669
         * Passes the values of the select area on to the appropriate 
 
1670
         * callback function on a click on apply button
 
1671
         * 
 
1672
         * @access private
 
1673
         * @return void
 
1674
         */
 
1675
        applyClick : function(ev) {
 
1676
                this.dragging = false;
 
1677
                this.resizing = false;
 
1678
                
 
1679
                this.options.onApplyClick(
 
1680
                        this.areaCoords,
 
1681
                        {
 
1682
                                width: this.calcW(), 
 
1683
                                height: this.calcH() 
 
1684
                        }
 
1685
                );
 
1686
                //ev.cancelBubble = true;
 
1687
                Event.stop( ev );
 
1688
        },
 
1689
        
 
1690
        /**
 
1691
         * Calls save callback
 
1692
         * 
 
1693
         * @access private
 
1694
         * @return void
 
1695
         */
 
1696
        saveClick : function(ev) {
 
1697
                this.dragging = false;
 
1698
                this.resizing = false;
 
1699
                
 
1700
                this.options.onSaveClick(
 
1701
                        this.areaCoords,
 
1702
                        {
 
1703
                                width: this.calcW(), 
 
1704
                                height: this.calcH() 
 
1705
                        }
 
1706
                );
 
1707
                //ev.cancelBubble = true;
 
1708
                Event.stop( ev );
 
1709
        },
 
1710
        
 
1711
        /**
 
1712
         * Abstract method called on the end of initialization
 
1713
         * 
 
1714
         * @access private
 
1715
         * @abstract
 
1716
         * @return void
 
1717
         */
 
1718
        subInitialize: function() {},
 
1719
        
 
1720
        /**
 
1721
         * Abstract method called on the end of drawArea()
 
1722
         * 
 
1723
         * @access private
 
1724
         * @abstract
 
1725
         * @return void
 
1726
         */
 
1727
        subDrawArea: function() {}
 
1728
};
 
1729
 
 
1730
 
 
1731
 
 
1732
 
 
1733
/**
 
1734
 * Extend the Cropper.Img class to allow for presentation of a preview image of the resulting crop,
 
1735
 * the option for displayOnInit is always overridden to true when displaying a preview image
 
1736
 * 
 
1737
 * Usage:
 
1738
 *      @param obj Image element to attach to
 
1739
 *      @param obj Optional options:
 
1740
 *              - see Cropper.Img for base options
 
1741
 *              
 
1742
 *              - previewWrap obj
 
1743
 *                      HTML element that will be used as a container for the preview image             
 
1744
 */
 
1745
Cropper.ImgWithPreview = Class.create();
 
1746
 
 
1747
Object.extend( Object.extend( Cropper.ImgWithPreview.prototype, Cropper.Img.prototype ), {
 
1748
        
 
1749
        /**
 
1750
         * Implements the abstract method from Cropper.Img to initialize preview image settings.
 
1751
         * Will only attach a preview image is the previewWrap element is defined and the minWidth
 
1752
         * & minHeight options are set.
 
1753
         * 
 
1754
         * @see Croper.Img.subInitialize
 
1755
         */
 
1756
        subInitialize: function() {
 
1757
                /**
 
1758
                 * Whether or not we've attached a preview image
 
1759
                 * @var boolean
 
1760
                 */
 
1761
                this.hasPreviewImg = false;
 
1762
                if( typeof(this.options.previewWrap) != 'undefined' 
 
1763
                        && this.options.minWidth > 0 
 
1764
                        && this.options.minHeight > 0
 
1765
                ) {
 
1766
                        /**
 
1767
                         * The preview image wrapper element
 
1768
                         * @var obj HTML element
 
1769
                         */
 
1770
                        this.previewWrap        = $( this.options.previewWrap );
 
1771
                        /**
 
1772
                         * The preview image element
 
1773
                         * @var obj HTML IMG element
 
1774
                         */
 
1775
                        this.previewImg         = this.img.cloneNode( false );
 
1776
                        // set the ID of the preview image to be unique
 
1777
                        this.previewImg.id      = 'imgCrop_' + this.previewImg.id;
 
1778
                        
 
1779
                                                
 
1780
                        // set the displayOnInit option to true so we display the select area at the same time as the thumbnail
 
1781
                        this.options.displayOnInit = true;
 
1782
 
 
1783
                        this.hasPreviewImg      = true;
 
1784
                        
 
1785
                        this.previewWrap.addClassName( 'imgCrop_previewWrap' );
 
1786
                        
 
1787
                        this.previewWrap.setStyle(
 
1788
                         { 
 
1789
                                width: this.options.minWidth + 'px',
 
1790
                                height: this.options.minHeight + 'px'
 
1791
                         }
 
1792
                        );
 
1793
                        
 
1794
                        this.previewWrap.appendChild( this.previewImg );
 
1795
                }
 
1796
        },
 
1797
        
 
1798
        /**
 
1799
         * Implements the abstract method from Cropper.Img to draw the preview image
 
1800
         * 
 
1801
         * @see Croper.Img.subDrawArea
 
1802
         */
 
1803
        subDrawArea: function() {
 
1804
                if( this.hasPreviewImg ) {
 
1805
                        // get the ratio of the select area to the src image
 
1806
                        var calcWidth = this.calcW();
 
1807
                        var calcHeight = this.calcH();
 
1808
                        // ratios for the dimensions of the preview image
 
1809
                        var dimRatio = { 
 
1810
                                x: this.imgW / calcWidth, 
 
1811
                                y: this.imgH / calcHeight 
 
1812
                        }; 
 
1813
                        //ratios for the positions within the preview
 
1814
                        var posRatio = { 
 
1815
                                x: calcWidth / this.options.minWidth, 
 
1816
                                y: calcHeight / this.options.minHeight 
 
1817
                        };
 
1818
                        
 
1819
                        // setting the positions in an obj before apply styles for rendering speed increase
 
1820
                        var calcPos     = {
 
1821
                                w: Math.ceil( this.options.minWidth * dimRatio.x ) + 'px',
 
1822
                                h: Math.ceil( this.options.minHeight * dimRatio.y ) + 'px',
 
1823
                                x: '-' + Math.ceil( this.areaCoords.x1 / posRatio.x )  + 'px',
 
1824
                                y: '-' + Math.ceil( this.areaCoords.y1 / posRatio.y ) + 'px'
 
1825
                        }
 
1826
                        
 
1827
                        var previewStyle        = this.previewImg.style;
 
1828
                        previewStyle.width      = calcPos.w;
 
1829
                        previewStyle.height     = calcPos.h;
 
1830
                        previewStyle.left       = calcPos.x;
 
1831
                        previewStyle.top        = calcPos.y;
 
1832
                }
 
1833
        }
 
1834
        
 
1835
});