]> git.mxchange.org Git - friendica.git/blob - view/js/cropper/cropper.uncompressed.js
Merge pull request #4704 from MrPetovan/bug/4608-hashtag-autocomplete-ci
[friendica.git] / view / js / cropper / cropper.uncompressed.js
1 /**\r
2  * Image Cropper (v. 1.2.0 - 2006-10-30 )\r
3  * Copyright (c) 2006 David Spurr (http://www.defusion.org.uk/)\r
4  * \r
5  * The image cropper provides a way to draw a crop area on an image and capture\r
6  * the coordinates of the drawn crop area.\r
7  * \r
8  * Features include:\r
9  *              - Based on Prototype and Scriptaculous\r
10  *              - Image editing package styling, the crop area functions and looks \r
11  *                like those found in popular image editing software\r
12  *              - Dynamic inclusion of required styles\r
13  *              - Drag to draw areas\r
14  *              - Shift drag to draw/resize areas as squares\r
15  *              - Selection area can be moved \r
16  *              - Seleciton area can be resized using resize handles\r
17  *              - Allows dimension ratio limited crop areas\r
18  *              - Allows minimum dimension crop areas\r
19  *              - Allows maximum dimesion crop areas\r
20  *              - If both min & max dimension options set to the same value for a single axis,then the cropper will not \r
21  *                display the resize handles as appropriate (when min & max dimensions are passed for both axes this\r
22  *                results in a 'fixed size' crop area)\r
23  *              - Allows dynamic preview of resultant crop ( if minimum width & height are provided ), this is\r
24  *                implemented as a subclass so can be excluded when not required\r
25  *              - Movement of selection area by arrow keys ( shift + arrow key will move selection area by\r
26  *                10 pixels )\r
27  *              - All operations stay within bounds of image\r
28  *              - All functionality & display compatible with most popular browsers supported by Prototype:\r
29  *                      PC:     IE 7, 6 & 5.5, Firefox 1.5, Opera 8.5 (see known issues) & 9.0b\r
30  *                      MAC: Camino 1.0, Firefox 1.5, Safari 2.0\r
31  * \r
32  * Requires:\r
33  *              - Prototype v. 1.5.0_rc0 > (as packaged with Scriptaculous 1.6.1)\r
34  *              - Scriptaculous v. 1.6.1 > modules: builder, dragdrop \r
35  *              \r
36  * Known issues:\r
37  *              - Safari animated gifs, only one of each will animate, this seems to be a known Safari issue\r
38  * \r
39  *              - After drawing an area and then clicking to start a new drag in IE 5.5 the rendered height \r
40  *        appears as the last height until the user drags, this appears to be the related to the error \r
41  *        that the forceReRender() method fixes for IE 6, i.e. IE 5.5 is not redrawing the box properly.\r
42  * \r
43  *              - Lack of CSS opacity support in Opera before version 9 mean we disable those style rules, these \r
44  *                could be fixed by using PNGs with transparency if Opera 8.5 support is high priority for you\r
45  * \r
46  *              - Marching ants keep reloading in IE <6 (not tested in IE7), it is a known issue in IE and I have \r
47  *        found no viable workarounds that can be included in the release. If this really is an issue for you\r
48  *        either try this post: http://mir.aculo.us/articles/2005/08/28/internet-explorer-and-ajax-image-caching-woes\r
49  *        or uncomment the 'FIX MARCHING ANTS IN IE' rules in the CSS file\r
50  *              \r
51  *              - Styling & borders on image, any CSS styling applied directly to the image itself (floats, borders, padding, margin, etc.) will \r
52  *                cause problems with the cropper. The use of a wrapper element to apply these styles to is recommended.\r
53  * \r
54  *              - overflow: auto or overflow: scroll on parent will cause cropper to burst out of parent in IE and Opera (maybe Mac browsers too)\r
55  *                I'm not sure why yet.\r
56  * \r
57  * Usage:\r
58  *              See Cropper.Img & Cropper.ImgWithPreview for usage details\r
59  * \r
60  * Changelog:\r
61  * v1.2.0 - 2006-10-30\r
62  *              + Added id to the preview image element using 'imgCrop_[originalImageID]'\r
63  *      * #00001 - Fixed bug: Doesn't account for scroll offsets\r
64  *      * #00009 - Fixed bug: Placing the cropper inside differently positioned elements causes incorrect co-ordinates and display\r
65  *      * #00013 - Fixed bug: I-bar cursor appears on drag plane\r
66  *      * #00014 - Fixed bug: If ID for image tag is not found in document script throws error\r
67  *      * Fixed bug with drag start co-ordinates if wrapper element has moved in browser (e.g. dragged to a new position)\r
68  *      * Fixed bug with drag start co-ordinates if image contained in a wrapper with scrolling - this may be buggy if image \r
69  *                has other ancestors with scrolling applied (except the body)\r
70  *      * #00015 - Fixed bug: When cropper removed and then reapplied onEndCrop callback gets called multiple times, solution suggestion from Bill Smith\r
71  *      * Various speed increases & code cleanup which meant improved performance in Mac - which allowed removal of different overlay methods for\r
72  *        IE and all other browsers, which led to a fix for:\r
73  *              * #00010 - Fixed bug: Select area doesn't adhere to image size when image resized using img attributes\r
74  *      - #00006 - Removed default behaviour of automatically setting a ratio when both min width & height passed, the ratioDimensions must be passed in\r
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\r
76  *        and the resize handles will not be displayed as appropriate\r
77  *              * Switched keydown for keypress for moving select area with cursor keys (makes for nicer action) - doesn't appear to work in Safari\r
78  * \r
79  * v1.1.3 - 2006-08-21\r
80  *              * Fixed wrong cursor on western handle in CSS\r
81  *              + #00008 & #00003 - Added feature: Allow to set dimensions & position for cropper on load\r
82  *      * #00002 - Fixed bug: Pressing 'remove cropper' twice removes image in IE\r
83  * \r
84  * v1.1.2 - 2006-06-09\r
85  *              * Fixed bugs with ratios when GCD is low (patch submitted by Andy Skelton)\r
86  * \r
87  * v1.1.1 - 2006-06-03\r
88  *              * Fixed bug with rendering issues fix in IE 5.5\r
89  *              * Fixed bug with endCrop callback issues once cropper had been removed & reset in IE\r
90  * \r
91  * v1.1.0 - 2006-06-02\r
92  *              * Fixed bug with IE constantly trying to reload select area background image\r
93  *              * Applied more robust fix to Safari & IE rendering issues\r
94  *              + Added method to reset parameters - useful for when dynamically changing img cropper attached to\r
95  *              + Added method to remove cropper from image\r
96  * \r
97  * v1.0.0 - 2006-05-18 \r
98  *              + Initial verison\r
99  * \r
100  * \r
101  * Copyright (c) 2006, David Spurr (http://www.defusion.org.uk/)\r
102  * All rights reserved.\r
103  * \r
104  * \r
105  * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\r
106  * \r
107  *     * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\r
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.\r
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.\r
110  * \r
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.\r
112  * \r
113  * http://www.opensource.org/licenses/bsd-license.php\r
114  * \r
115  * See scriptaculous.js for full scriptaculous licence\r
116  *\r
117  * Modified 2013/06/01 Zach P to change $() to $PR() for eliminating conflicts with jQuery\r
118  */\r
119  \r
120 /**\r
121  * Extend the Draggable class to allow us to pass the rendering\r
122  * down to the Cropper object.\r
123  */\r
124 var CropDraggable = Class.create();\r
125 \r
126 Object.extend( Object.extend( CropDraggable.prototype, Draggable.prototype), {\r
127         \r
128         initialize: function(element) {\r
129                 this.options = Object.extend(\r
130                         {\r
131                                 /**\r
132                                  * The draw method to defer drawing to\r
133                                  */\r
134                                 drawMethod: function() {}\r
135                         }, \r
136                         arguments[1] || {}\r
137                 );\r
138 \r
139                 this.element = $PR(element);\r
140 \r
141                 this.handle = this.element;\r
142 \r
143                 this.delta    = this.currentDelta();\r
144                 this.dragging = false;   \r
145 \r
146                 this.eventMouseDown = this.initDrag.bindAsEventListener(this);\r
147                 Event.observe(this.handle, "mousedown", this.eventMouseDown);\r
148 \r
149                 Draggables.register(this);\r
150         },\r
151         \r
152         /**\r
153          * Defers the drawing of the draggable to the supplied method\r
154          */\r
155         draw: function(point) {\r
156                 var pos = Position.cumulativeOffset(this.element);\r
157                 var d = this.currentDelta();\r
158                 pos[0] -= d[0]; \r
159                 pos[1] -= d[1];\r
160                                 \r
161                 var p = [0,1].map(function(i) { \r
162                         return (point[i]-pos[i]-this.offset[i]) \r
163                 }.bind(this));\r
164                                 \r
165                 this.options.drawMethod( p );\r
166         }\r
167         \r
168 });\r
169 \r
170 \r
171 /**\r
172  * The Cropper object, this will attach itself to the provided image by wrapping it with \r
173  * the generated xHTML structure required by the cropper.\r
174  * \r
175  * Usage:\r
176  *      @param obj Image element to attach to\r
177  *      @param obj Optional options:\r
178  *              - ratioDim obj \r
179  *                      The pixel dimensions to apply as a restrictive ratio, with properties x & y\r
180  * \r
181  *              - minWidth int \r
182  *                      The minimum width for the select area in pixels\r
183  * \r
184  *              - minHeight     int \r
185  *                      The mimimum height for the select area in pixels\r
186  * \r
187  *              - maxWidth int\r
188  *                      The maximum width for the select areas in pixels (if both minWidth & maxWidth set to same the width of the cropper will be fixed)\r
189  * \r
190  *              - maxHeight int\r
191  *                      The maximum height for the select areas in pixels (if both minHeight & maxHeight set to same the height of the cropper will be fixed)\r
192  * \r
193  *              - displayOnInit int \r
194  *                      Whether to display the select area on initialisation, only used when providing minimum width & height or ratio\r
195  * \r
196  *              - onEndCrop func\r
197  *                      The callback function to provide the crop details to on end of a crop (see below)\r
198  * \r
199  *              - captureKeys boolean\r
200  *                      Whether to capture the keys for moving the select area, as these can cause some problems at the moment\r
201  * \r
202  *              - onloadCoords obj\r
203  *                      A coordinates object with properties x1, y1, x2 & y2; for the coordinates of the select area to display onload\r
204  *      \r
205  *----------------------------------------------\r
206  * \r
207  * The callback function provided via the onEndCrop option should accept the following parameters:\r
208  *              - coords obj\r
209  *                      The coordinates object with properties x1, y1, x2 & y2; for the coordinates of the select area\r
210  * \r
211  *              - dimensions obj\r
212  *                      The dimensions object with properites width & height; for the dimensions of the select area\r
213  *              \r
214  *\r
215  *              Example:\r
216  *                      function onEndCrop( coords, dimensions ) {\r
217  *                              $PR( 'x1' ).value       = coords.x1;\r
218  *                              $PR( 'y1' ).value       = coords.y1;\r
219  *                              $PR( 'x2' ).value       = coords.x2;\r
220  *                              $PR( 'y2' ).value       = coords.y2;\r
221  *                              $PR( 'width' ).value    = dimensions.width;\r
222  *                              $PR( 'height' ).value   = dimensions.height;\r
223  *                      }\r
224  * \r
225  */\r
226 var Cropper = {};\r
227 Cropper.Img = Class.create();\r
228 Cropper.Img.prototype = {\r
229         \r
230         /**\r
231          * Initialises the class\r
232          * \r
233          * @access public\r
234          * @param obj Image element to attach to\r
235          * @param obj Options\r
236          * @return void\r
237          */\r
238         initialize: function(element, options) {\r
239                 this.options = Object.extend(\r
240                         {\r
241                                 /**\r
242                                  * @var obj\r
243                                  * The pixel dimensions to apply as a restrictive ratio\r
244                                  */\r
245                                 ratioDim: { x: 0, y: 0 },\r
246                                 /**\r
247                                  * @var int\r
248                                  * The minimum pixel width, also used as restrictive ratio if min height passed too\r
249                                  */\r
250                                 minWidth:               0,\r
251                                 /**\r
252                                  * @var int\r
253                                  * The minimum pixel height, also used as restrictive ratio if min width passed too\r
254                                  */\r
255                                 minHeight:              0,\r
256                                 /**\r
257                                  * @var boolean\r
258                                  * Whether to display the select area on initialisation, only used when providing minimum width & height or ratio\r
259                                  */\r
260                                 displayOnInit:  false,\r
261                                 /**\r
262                                  * @var function\r
263                                  * The call back function to pass the final values to\r
264                                  */\r
265                                 onEndCrop: Prototype.emptyFunction,\r
266                                 /**\r
267                                  * @var boolean\r
268                                  * Whether to capture key presses or not\r
269                                  */\r
270                                 captureKeys: true,\r
271                                 /**\r
272                                  * @var obj Coordinate object x1, y1, x2, y2\r
273                                  * The coordinates to optionally display the select area at onload\r
274                                  */\r
275                                 onloadCoords: null,\r
276                                 /**\r
277                                  * @var int\r
278                                  * The maximum width for the select areas in pixels (if both minWidth & maxWidth set to same the width of the cropper will be fixed)\r
279                                  */\r
280                                 maxWidth: 0,\r
281                                 /**\r
282                                  * @var int\r
283                                  * The maximum height for the select areas in pixels (if both minHeight & maxHeight set to same the height of the cropper will be fixed)\r
284                                  */\r
285                                 maxHeight: 0\r
286                         }, \r
287                         options || {}\r
288                 );                              \r
289                 /**\r
290                  * @var obj\r
291                  * The img node to attach to\r
292                  */\r
293                 this.img                        = $PR( element );\r
294                 /**\r
295                  * @var obj\r
296                  * The x & y coordinates of the click point\r
297                  */\r
298                 this.clickCoords        = { x: 0, y: 0 };\r
299                 /**\r
300                  * @var boolean\r
301                  * Whether the user is dragging\r
302                  */\r
303                 this.dragging           = false;\r
304                 /**\r
305                  * @var boolean\r
306                  * Whether the user is resizing\r
307                  */\r
308                 this.resizing           = false;\r
309                 /**\r
310                  * @var boolean\r
311                  * Whether the user is on a webKit browser\r
312                  */\r
313                 this.isWebKit           = /Konqueror|Safari|KHTML/.test( navigator.userAgent );\r
314                 /**\r
315                  * @var boolean\r
316                  * Whether the user is on IE\r
317                  */\r
318                 this.isIE                       = /MSIE/.test( navigator.userAgent );\r
319                 /**\r
320                  * @var boolean\r
321                  * Whether the user is on Opera below version 9\r
322                  */\r
323                 this.isOpera8           = /Opera\s[1-8]/.test( navigator.userAgent );\r
324                 /**\r
325                  * @var int\r
326                  * The x ratio \r
327                  */\r
328                 this.ratioX                     = 0;\r
329                 /**\r
330                  * @var int\r
331                  * The y ratio\r
332                  */\r
333                 this.ratioY                     = 0;\r
334                 /**\r
335                  * @var boolean\r
336                  * Whether we've attached sucessfully\r
337                  */\r
338                 this.attached           = false;\r
339                 /**\r
340                  * @var boolean\r
341                  * Whether we've got a fixed width (if minWidth EQ or GT maxWidth then we have a fixed width\r
342                  * in the case of minWidth > maxWidth maxWidth wins as the fixed width)\r
343                  */\r
344                 this.fixedWidth         = ( this.options.maxWidth > 0 && ( this.options.minWidth >= this.options.maxWidth ) );\r
345                 /**\r
346                  * @var boolean\r
347                  * Whether we've got a fixed height (if minHeight EQ or GT maxHeight then we have a fixed height\r
348                  * in the case of minHeight > maxHeight maxHeight wins as the fixed height)\r
349                  */\r
350                 this.fixedHeight        = ( this.options.maxHeight > 0 && ( this.options.minHeight >= this.options.maxHeight ) );\r
351                 \r
352                 // quit if the image element doesn't exist\r
353                 if( typeof this.img == 'undefined' ) return;\r
354                                 \r
355                 // include the stylesheet               \r
356                 $A( document.getElementsByTagName( 'script' ) ).each( \r
357                         function(s) {\r
358                                 if( s.src.match( /cropper\.js/ ) ) {\r
359                                         var path        = s.src.replace( /cropper\.js(.*)?/, '' );\r
360                                         // '<link rel="stylesheet" type="text/css" href="' + path + 'cropper.css" media="screen" />';\r
361                                         var style               = document.createElement( 'link' );\r
362                                         style.rel               = 'stylesheet';\r
363                                         style.type              = 'text/css';\r
364                                         style.href              = path + 'cropper.css';\r
365                                         style.media     = 'screen';\r
366                                         document.getElementsByTagName( 'head' )[0].appendChild( style );\r
367                                 }\r
368                 }\r
369             );   \r
370         \r
371                 // calculate the ratio when neccessary\r
372                 if( this.options.ratioDim.x > 0 && this.options.ratioDim.y > 0 ) {\r
373                         var gcd = this.getGCD( this.options.ratioDim.x, this.options.ratioDim.y );\r
374                         this.ratioX = this.options.ratioDim.x / gcd;\r
375                         this.ratioY = this.options.ratioDim.y / gcd;\r
376                         // dump( 'RATIO : ' + this.ratioX + ':' + this.ratioY + '\n' );\r
377                 }\r
378                                                         \r
379                 // initialise sub classes\r
380                 this.subInitialize();\r
381 \r
382                 // only load the event observers etc. once the image is loaded\r
383                 // this is done after the subInitialize() call just in case the sub class does anything\r
384                 // that will affect the result of the call to onLoad()\r
385                 if( this.img.complete || this.isWebKit ) this.onLoad(); // for some reason Safari seems to support img.complete but returns 'undefined' on the this.img object\r
386                 else Event.observe( this.img, 'load', this.onLoad.bindAsEventListener( this) );         \r
387         },\r
388         \r
389         /**\r
390          * The Euclidean algorithm used to find the greatest common divisor\r
391          * \r
392          * @acces private\r
393          * @param int Value 1\r
394          * @param int Value 2\r
395          * @return int\r
396          */\r
397         getGCD : function( a , b ) {\r
398                 if( b == 0 ) return a;\r
399                 return this.getGCD(b, a % b );\r
400         },\r
401         \r
402         /**\r
403          * Attaches the cropper to the image once it has loaded\r
404          * \r
405          * @access private\r
406          * @return void\r
407          */\r
408         onLoad: function( ) {\r
409                 /*\r
410                  * Build the container and all related elements, will result in the following\r
411                  *\r
412                  * <div class="imgCrop_wrap">\r
413                  *              <img ... this.img ... />\r
414                  *              <div class="imgCrop_dragArea">\r
415                  *                      <!-- the inner spans are only required for IE to stop it making the divs 1px high/wide -->\r
416                  *                      <div class="imgCrop_overlay imageCrop_north"><span></span></div>\r
417                  *                      <div class="imgCrop_overlay imageCrop_east"><span></span></div>\r
418                  *                      <div class="imgCrop_overlay imageCrop_south"><span></span></div>\r
419                  *                      <div class="imgCrop_overlay imageCrop_west"><span></span></div>\r
420                  *                      <div class="imgCrop_selArea">\r
421                  *                              <!-- marquees -->\r
422                  *                              <!-- the inner spans are only required for IE to stop it making the divs 1px high/wide -->\r
423                  *                              <div class="imgCrop_marqueeHoriz imgCrop_marqueeNorth"><span></span></div>\r
424                  *                              <div class="imgCrop_marqueeVert imgCrop_marqueeEast"><span></span></div>\r
425                  *                              <div class="imgCrop_marqueeHoriz imgCrop_marqueeSouth"><span></span></div>\r
426                  *                              <div class="imgCrop_marqueeVert imgCrop_marqueeWest"><span></span></div>                        \r
427                  *                              <!-- handles -->\r
428                  *                              <div class="imgCrop_handle imgCrop_handleN"></div>\r
429                  *                              <div class="imgCrop_handle imgCrop_handleNE"></div>\r
430                  *                              <div class="imgCrop_handle imgCrop_handleE"></div>\r
431                  *                              <div class="imgCrop_handle imgCrop_handleSE"></div>\r
432                  *                              <div class="imgCrop_handle imgCrop_handleS"></div>\r
433                  *                              <div class="imgCrop_handle imgCrop_handleSW"></div>\r
434                  *                              <div class="imgCrop_handle imgCrop_handleW"></div>\r
435                  *                              <div class="imgCrop_handle imgCrop_handleNW"></div>\r
436                  *                              <div class="imgCrop_clickArea"></div>\r
437                  *                      </div>  \r
438                  *                      <div class="imgCrop_clickArea"></div>\r
439                  *              </div>  \r
440                  * </div>\r
441                  */\r
442                 var cNamePrefix = 'imgCrop_';\r
443                 \r
444                 // get the point to insert the container\r
445                 var insertPoint = this.img.parentNode;\r
446                 \r
447                 // apply an extra class to the wrapper to fix Opera below version 9\r
448                 var fixOperaClass = '';\r
449                 if( this.isOpera8 ) fixOperaClass = ' opera8';\r
450                 this.imgWrap = Builder.node( 'div', { 'class': cNamePrefix + 'wrap' + fixOperaClass } );\r
451                 \r
452                 this.north              = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'north' }, [Builder.node( 'span' )] );\r
453                 this.east               = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'east' } , [Builder.node( 'span' )] );\r
454                 this.south              = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'south' }, [Builder.node( 'span' )] );\r
455                 this.west               = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'west' } , [Builder.node( 'span' )] );\r
456                 \r
457                 var overlays    = [ this.north, this.east, this.south, this.west ];\r
458 \r
459                 this.dragArea   = Builder.node( 'div', { 'class': cNamePrefix + 'dragArea' }, overlays );\r
460                                                 \r
461                 this.handleN    = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleN' } );\r
462                 this.handleNE   = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleNE' } );\r
463                 this.handleE    = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleE' } );\r
464                 this.handleSE   = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleSE' } );\r
465                 this.handleS    = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleS' } );\r
466                 this.handleSW   = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleSW' } );\r
467                 this.handleW    = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleW' } );\r
468                 this.handleNW   = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleNW' } );\r
469                                 \r
470                 this.selArea    = Builder.node( 'div', { 'class': cNamePrefix + 'selArea' },\r
471                         [\r
472                                 Builder.node( 'div', { 'class': cNamePrefix + 'marqueeHoriz ' + cNamePrefix + 'marqueeNorth' }, [Builder.node( 'span' )] ),\r
473                                 Builder.node( 'div', { 'class': cNamePrefix + 'marqueeVert ' + cNamePrefix + 'marqueeEast' }  , [Builder.node( 'span' )] ),\r
474                                 Builder.node( 'div', { 'class': cNamePrefix + 'marqueeHoriz ' + cNamePrefix + 'marqueeSouth' }, [Builder.node( 'span' )] ),\r
475                                 Builder.node( 'div', { 'class': cNamePrefix + 'marqueeVert ' + cNamePrefix + 'marqueeWest' }  , [Builder.node( 'span' )] ),\r
476                                 this.handleN,\r
477                                 this.handleNE,\r
478                                 this.handleE,\r
479                                 this.handleSE,\r
480                                 this.handleS,\r
481                                 this.handleSW,\r
482                                 this.handleW,\r
483                                 this.handleNW,\r
484                                 Builder.node( 'div', { 'class': cNamePrefix + 'clickArea' } )\r
485                         ]\r
486                 );\r
487                                 \r
488                 this.imgWrap.appendChild( this.img );\r
489                 this.imgWrap.appendChild( this.dragArea );\r
490                 this.dragArea.appendChild( this.selArea );\r
491                 this.dragArea.appendChild( Builder.node( 'div', { 'class': cNamePrefix + 'clickArea' } ) );\r
492 \r
493                 insertPoint.appendChild( this.imgWrap );\r
494 \r
495                 // add event observers\r
496                 this.startDragBind      = this.startDrag.bindAsEventListener( this );\r
497                 Event.observe( this.dragArea, 'mousedown', this.startDragBind );\r
498                 \r
499                 this.onDragBind         = this.onDrag.bindAsEventListener( this );\r
500                 Event.observe( document, 'mousemove', this.onDragBind );\r
501                 \r
502                 this.endCropBind        = this.endCrop.bindAsEventListener( this );\r
503                 Event.observe( document, 'mouseup', this.endCropBind );\r
504                 \r
505                 this.resizeBind         = this.startResize.bindAsEventListener( this );\r
506                 this.handles = [ this.handleN, this.handleNE, this.handleE, this.handleSE, this.handleS, this.handleSW, this.handleW, this.handleNW ];\r
507                 this.registerHandles( true );\r
508                 \r
509                 if( this.options.captureKeys ) {\r
510                         this.keysBind = this.handleKeys.bindAsEventListener( this );\r
511                         Event.observe( document, 'keypress', this.keysBind );\r
512                 }\r
513 \r
514                 // attach the dragable to the select area\r
515                 new CropDraggable( this.selArea, { drawMethod: this.moveArea.bindAsEventListener( this ) } );\r
516                 \r
517                 this.setParams();\r
518         },\r
519         \r
520         /**\r
521          * Manages adding or removing the handle event handler and hiding or displaying them as appropriate\r
522          * \r
523          * @access private\r
524          * @param boolean registration true = add, false = remove\r
525          * @return void\r
526          */\r
527         registerHandles: function( registration ) {     \r
528                 for( var i = 0; i < this.handles.length; i++ ) {\r
529                         var handle = $PR( this.handles[i] );\r
530                         \r
531                         if( registration ) {\r
532                                 var hideHandle  = false;        // whether to hide the handle\r
533                                 \r
534                                 // disable handles asappropriate if we've got fixed dimensions\r
535                                 // if both dimensions are fixed we don't need to do much\r
536                                 if( this.fixedWidth && this.fixedHeight ) hideHandle = true;\r
537                                 else if( this.fixedWidth || this.fixedHeight ) {\r
538                                         // if one of the dimensions is fixed then just hide those handles\r
539                                         var isCornerHandle      = handle.className.match( /([S|N][E|W])$/ )\r
540                                         var isWidthHandle       = handle.className.match( /(E|W)$/ );\r
541                                         var isHeightHandle      = handle.className.match( /(N|S)$/ );\r
542                                         if( isCornerHandle ) hideHandle = true;\r
543                                         else if( this.fixedWidth && isWidthHandle ) hideHandle = true;\r
544                                         else if( this.fixedHeight && isHeightHandle ) hideHandle = true;\r
545                                 }\r
546                                 if( hideHandle ) handle.hide();\r
547                                 else Event.observe( handle, 'mousedown', this.resizeBind );\r
548                         } else {\r
549                                 handle.show();\r
550                                 Event.stopObserving( handle, 'mousedown', this.resizeBind );\r
551                         }\r
552                 }\r
553         },\r
554                 \r
555         /**\r
556          * Sets up all the cropper parameters, this can be used to reset the cropper when dynamically\r
557          * changing the images\r
558          * \r
559          * @access private\r
560          * @return void\r
561          */\r
562         setParams: function() {\r
563                 /**\r
564                  * @var int\r
565                  * The image width\r
566                  */\r
567                 this.imgW = this.img.width;\r
568                 /**\r
569                  * @var int\r
570                  * The image height\r
571                  */\r
572                 this.imgH = this.img.height;                    \r
573 \r
574                 $PR( this.north ).setStyle( { height: 0 } );\r
575                 $PR( this.east ).setStyle( { width: 0, height: 0 } );\r
576                 $PR( this.south ).setStyle( { height: 0 } );\r
577                 $PR( this.west ).setStyle( { width: 0, height: 0 } );\r
578                 \r
579                 // resize the container to fit the image\r
580                 $PR( this.imgWrap ).setStyle( { 'width': this.imgW + 'px', 'height': this.imgH + 'px' } );\r
581                 \r
582                 // hide the select area\r
583                 $PR( this.selArea ).hide();\r
584                                                 \r
585                 // setup the starting position of the select area\r
586                 var startCoords = { x1: 0, y1: 0, x2: 0, y2: 0 };\r
587                 var validCoordsSet = false;\r
588                 \r
589                 // display the select area \r
590                 if( this.options.onloadCoords != null ) {\r
591                         // if we've being given some coordinates to \r
592                         startCoords = this.cloneCoords( this.options.onloadCoords );\r
593                         validCoordsSet = true;\r
594                 } else if( this.options.ratioDim.x > 0 && this.options.ratioDim.y > 0 ) {\r
595                         // if there is a ratio limit applied and the then set it to initial ratio\r
596                         startCoords.x1 = Math.ceil( ( this.imgW - this.options.ratioDim.x ) / 2 );\r
597                         startCoords.y1 = Math.ceil( ( this.imgH - this.options.ratioDim.y ) / 2 );\r
598                         startCoords.x2 = startCoords.x1 + this.options.ratioDim.x;\r
599                         startCoords.y2 = startCoords.y1 + this.options.ratioDim.y;\r
600                         validCoordsSet = true;\r
601                 }\r
602                 \r
603                 this.setAreaCoords( startCoords, false, false, 1 );\r
604                 \r
605                 if( this.options.displayOnInit && validCoordsSet ) {\r
606                         this.selArea.show();\r
607                         this.drawArea();\r
608                         this.endCrop();\r
609                 }\r
610                 \r
611                 this.attached = true;\r
612         },\r
613         \r
614         /**\r
615          * Removes the cropper\r
616          * \r
617          * @access public\r
618          * @return void\r
619          */\r
620         remove: function() {\r
621                 if( this.attached ) {\r
622                         this.attached = false;\r
623                         \r
624                         // remove the elements we inserted\r
625                         this.imgWrap.parentNode.insertBefore( this.img, this.imgWrap );\r
626                         this.imgWrap.parentNode.removeChild( this.imgWrap );\r
627                         \r
628                         // remove the event observers\r
629                         Event.stopObserving( this.dragArea, 'mousedown', this.startDragBind );\r
630                         Event.stopObserving( document, 'mousemove', this.onDragBind );          \r
631                         Event.stopObserving( document, 'mouseup', this.endCropBind );\r
632                         this.registerHandles( false );\r
633                         if( this.options.captureKeys ) Event.stopObserving( document, 'keypress', this.keysBind );\r
634                 }\r
635         },\r
636         \r
637         /**\r
638          * Resets the cropper, can be used either after being removed or any time you wish\r
639          * \r
640          * @access public\r
641          * @return void\r
642          */\r
643         reset: function() {\r
644                 if( !this.attached ) this.onLoad();\r
645                 else this.setParams();\r
646                 this.endCrop();\r
647         },\r
648         \r
649         /**\r
650          * Handles the key functionality, currently just using arrow keys to move, if the user\r
651          * presses shift then the area will move by 10 pixels\r
652          */\r
653         handleKeys: function( e ) {\r
654                 var dir = { x: 0, y: 0 }; // direction to move it in & the amount in pixels\r
655                 if( !this.dragging ) {\r
656                         \r
657                         // catch the arrow keys\r
658                         switch( e.keyCode ) {\r
659                                 case( 37 ) : // left\r
660                                         dir.x = -1;\r
661                                         break;\r
662                                 case( 38 ) : // up\r
663                                         dir.y = -1;\r
664                                         break;\r
665                                 case( 39 ) : // right\r
666                                         dir.x = 1;\r
667                                         break\r
668                                 case( 40 ) : // down\r
669                                         dir.y = 1;\r
670                                         break;\r
671                         }\r
672                         \r
673                         if( dir.x != 0 || dir.y != 0 ) {\r
674                                 // if shift is pressed then move by 10 pixels\r
675                                 if( e.shiftKey ) {\r
676                                         dir.x *= 10;\r
677                                         dir.y *= 10;\r
678                                 }\r
679                                 \r
680                                 this.moveArea( [ this.areaCoords.x1 + dir.x, this.areaCoords.y1 + dir.y ] );\r
681                                 Event.stop( e ); \r
682                         }\r
683                 }\r
684         },\r
685         \r
686         /**\r
687          * Calculates the width from the areaCoords\r
688          * \r
689          * @access private\r
690          * @return int\r
691          */\r
692         calcW: function() {\r
693                 return (this.areaCoords.x2 - this.areaCoords.x1)\r
694         },\r
695         \r
696         /**\r
697          * Calculates the height from the areaCoords\r
698          * \r
699          * @access private\r
700          * @return int\r
701          */\r
702         calcH: function() {\r
703                 return (this.areaCoords.y2 - this.areaCoords.y1)\r
704         },\r
705         \r
706         /**\r
707          * Moves the select area to the supplied point (assumes the point is x1 & y1 of the select area)\r
708          * \r
709          * @access public\r
710          * @param array Point for x1 & y1 to move select area to\r
711          * @return void\r
712          */\r
713         moveArea: function( point ) {\r
714                 // dump( 'moveArea        : ' + point[0] + ',' + point[1] + ',' + ( point[0] + ( this.areaCoords.x2 - this.areaCoords.x1 ) ) + ',' + ( point[1] + ( this.areaCoords.y2 - this.areaCoords.y1 ) ) + '\n' );\r
715                 this.setAreaCoords( \r
716                         {\r
717                                 x1: point[0], \r
718                                 y1: point[1],\r
719                                 x2: point[0] + this.calcW(),\r
720                                 y2: point[1] + this.calcH()\r
721                         },\r
722                         true,\r
723                         false\r
724                 );\r
725                 this.drawArea();\r
726         },\r
727 \r
728         /**\r
729          * Clones a co-ordinates object, stops problems with handling them by reference\r
730          * \r
731          * @access private\r
732          * @param obj Coordinate object x1, y1, x2, y2\r
733          * @return obj Coordinate object x1, y1, x2, y2\r
734          */\r
735         cloneCoords: function( coords ) {\r
736                 return { x1: coords.x1, y1: coords.y1, x2: coords.x2, y2: coords.y2 };\r
737         },\r
738 \r
739         /**\r
740          * Sets the select coords to those provided but ensures they don't go\r
741          * outside the bounding box\r
742          * \r
743          * @access private\r
744          * @param obj Coordinates x1, y1, x2, y2\r
745          * @param boolean Whether this is a move\r
746          * @param boolean Whether to apply squaring\r
747          * @param obj Direction of mouse along both axis x, y ( -1 = negative, 1 = positive ) only required when moving etc.\r
748          * @param string The current resize handle || null\r
749          * @return void\r
750          */\r
751         setAreaCoords: function( coords, moving, square, direction, resizeHandle ) {\r
752                 // dump( 'setAreaCoords (in) : ' + coords.x1 + ',' + coords.y1 + ',' + coords.x2 + ',' + coords.y2 );\r
753                 if( moving ) {\r
754                         // if moving\r
755                         var targW = coords.x2 - coords.x1;\r
756                         var targH = coords.y2 - coords.y1;\r
757                         \r
758                         // ensure we're within the bounds\r
759                         if( coords.x1 < 0 ) {\r
760                                 coords.x1 = 0;\r
761                                 coords.x2 = targW;\r
762                         }\r
763                         if( coords.y1 < 0 ) {\r
764                                 coords.y1 = 0;\r
765                                 coords.y2 = targH;\r
766                         }\r
767                         if( coords.x2 > this.imgW ) {\r
768                                 coords.x2 = this.imgW;\r
769                                 coords.x1 = this.imgW - targW;\r
770                         }\r
771                         if( coords.y2 > this.imgH ) {\r
772                                 coords.y2 = this.imgH;\r
773                                 coords.y1 = this.imgH - targH;\r
774                         }                       \r
775                 } else {\r
776                         // ensure we're within the bounds\r
777                         if( coords.x1 < 0 ) coords.x1 = 0;\r
778                         if( coords.y1 < 0 ) coords.y1 = 0;\r
779                         if( coords.x2 > this.imgW ) coords.x2 = this.imgW;\r
780                         if( coords.y2 > this.imgH ) coords.y2 = this.imgH;\r
781                         \r
782                         // This is passed as null in onload\r
783                         if( direction != null ) {\r
784                                                                 \r
785                                 // apply the ratio or squaring where appropriate\r
786                                 if( this.ratioX > 0 ) this.applyRatio( coords, { x: this.ratioX, y: this.ratioY }, direction, resizeHandle );\r
787                                 else if( square ) this.applyRatio( coords, { x: 1, y: 1 }, direction, resizeHandle );\r
788                                                                                 \r
789                                 var mins = [ this.options.minWidth, this.options.minHeight ]; // minimum dimensions [x,y]                       \r
790                                 var maxs = [ this.options.maxWidth, this.options.maxHeight ]; // maximum dimensions [x,y]\r
791                 \r
792                                 // apply dimensions where appropriate\r
793                                 if( mins[0] > 0 || mins[1] > 0 || maxs[0] > 0 || maxs[1] > 0) {\r
794                                 \r
795                                         var coordsTransX        = { a1: coords.x1, a2: coords.x2 };\r
796                                         var coordsTransY        = { a1: coords.y1, a2: coords.y2 };\r
797                                         var boundsX                     = { min: 0, max: this.imgW };\r
798                                         var boundsY                     = { min: 0, max: this.imgH };\r
799                                         \r
800                                         // handle squaring properly on single axis minimum dimensions\r
801                                         if( (mins[0] != 0 || mins[1] != 0) && square ) {\r
802                                                 if( mins[0] > 0 ) mins[1] = mins[0];\r
803                                                 else if( mins[1] > 0 ) mins[0] = mins[1];\r
804                                         }\r
805                                         \r
806                                         if( (maxs[0] != 0 || maxs[0] != 0) && square ) {\r
807                                                 // 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)\r
808                                                 if( maxs[0] > 0 && maxs[0] <= maxs[1] ) maxs[1] = maxs[0];\r
809                                                 else if( maxs[1] > 0 && maxs[1] <= maxs[0] ) maxs[0] = maxs[1];\r
810                                         }\r
811                                         \r
812                                         if( mins[0] > 0 ) this.applyDimRestriction( coordsTransX, mins[0], direction.x, boundsX, 'min' );\r
813                                         if( mins[1] > 1 ) this.applyDimRestriction( coordsTransY, mins[1], direction.y, boundsY, 'min' );\r
814                                         \r
815                                         if( maxs[0] > 0 ) this.applyDimRestriction( coordsTransX, maxs[0], direction.x, boundsX, 'max' );\r
816                                         if( maxs[1] > 1 ) this.applyDimRestriction( coordsTransY, maxs[1], direction.y, boundsY, 'max' );\r
817                                         \r
818                                         coords = { x1: coordsTransX.a1, y1: coordsTransY.a1, x2: coordsTransX.a2, y2: coordsTransY.a2 };\r
819                                 }\r
820                                 \r
821                         }\r
822                 }\r
823                 \r
824                 // dump( 'setAreaCoords (out) : ' + coords.x1 + ',' + coords.y1 + ',' + coords.x2 + ',' + coords.y2 + '\n' );\r
825                 this.areaCoords = coords;\r
826         },\r
827         \r
828         /**\r
829          * Applies the supplied dimension restriction to the supplied coordinates along a single axis\r
830          * \r
831          * @access private\r
832          * @param obj Single axis coordinates, a1, a2 (e.g. for the x axis a1 = x1 & a2 = x2)\r
833          * @param int The restriction value\r
834          * @param int The direction ( -1 = negative, 1 = positive )\r
835          * @param obj The bounds of the image ( for this axis )\r
836          * @param string The dimension restriction type ( 'min' | 'max' )\r
837          * @return void\r
838          */\r
839         applyDimRestriction: function( coords, val, direction, bounds, type ) {\r
840                 var check;\r
841                 if( type == 'min' ) check = ( ( coords.a2 - coords.a1 ) < val );\r
842                 else check = ( ( coords.a2 - coords.a1 ) > val );\r
843                 if( check ) {\r
844                         if( direction == 1 ) coords.a2 = coords.a1 + val;\r
845                         else coords.a1 = coords.a2 - val;\r
846                         \r
847                         // make sure we're still in the bounds (not too pretty for the user, but needed)\r
848                         if( coords.a1 < bounds.min ) {\r
849                                 coords.a1 = bounds.min;\r
850                                 coords.a2 = val;\r
851                         } else if( coords.a2 > bounds.max ) {\r
852                                 coords.a1 = bounds.max - val;\r
853                                 coords.a2 = bounds.max;\r
854                         }\r
855                 }\r
856         },\r
857                 \r
858         /**\r
859          * Applies the supplied ratio to the supplied coordinates\r
860          * \r
861          * @access private\r
862          * @param obj Coordinates, x1, y1, x2, y2\r
863          * @param obj Ratio, x, y\r
864          * @param obj Direction of mouse, x & y : -1 == negative 1 == positive\r
865          * @param string The current resize handle || null\r
866          * @return void\r
867          */\r
868         applyRatio : function( coords, ratio, direction, resizeHandle ) {\r
869                 // dump( 'direction.y : ' + direction.y + '\n');\r
870                 var newCoords;\r
871                 if( resizeHandle == 'N' || resizeHandle == 'S' ) {\r
872                         // dump( 'north south \n');\r
873                         // if moving on either the lone north & south handles apply the ratio on the y axis\r
874                         newCoords = this.applyRatioToAxis( \r
875                                 { a1: coords.y1, b1: coords.x1, a2: coords.y2, b2: coords.x2 },\r
876                                 { a: ratio.y, b: ratio.x },\r
877                                 { a: direction.y, b: direction.x },\r
878                                 { min: 0, max: this.imgW }\r
879                         );\r
880                         coords.x1 = newCoords.b1;\r
881                         coords.y1 = newCoords.a1;\r
882                         coords.x2 = newCoords.b2;\r
883                         coords.y2 = newCoords.a2;\r
884                 } else {\r
885                         // otherwise deal with it as if we're applying the ratio on the x axis\r
886                         newCoords = this.applyRatioToAxis( \r
887                                 { a1: coords.x1, b1: coords.y1, a2: coords.x2, b2: coords.y2 },\r
888                                 { a: ratio.x, b: ratio.y },\r
889                                 { a: direction.x, b: direction.y },\r
890                                 { min: 0, max: this.imgH }\r
891                         );\r
892                         coords.x1 = newCoords.a1;\r
893                         coords.y1 = newCoords.b1;\r
894                         coords.x2 = newCoords.a2;\r
895                         coords.y2 = newCoords.b2;\r
896                 }\r
897                 \r
898         },\r
899         \r
900         /**\r
901          * Applies the provided ratio to the provided coordinates based on provided direction & bounds,\r
902          * use to encapsulate functionality to make it easy to apply to either axis. This is probably\r
903          * quite hard to visualise so see the x axis example within applyRatio()\r
904          * \r
905          * Example in parameter details & comments is for requesting applying ratio to x axis.\r
906          * \r
907          * @access private\r
908          * @param obj Coords object (a1, b1, a2, b2) where a = x & b = y in example\r
909          * @param obj Ratio object (a, b) where a = x & b = y in example\r
910          * @param obj Direction object (a, b) where a = x & b = y in example\r
911          * @param obj Bounds (min, max)\r
912          * @return obj Coords object (a1, b1, a2, b2) where a = x & b = y in example\r
913          */\r
914         applyRatioToAxis: function( coords, ratio, direction, bounds ) {\r
915                 var newCoords = Object.extend( coords, {} );\r
916                 var calcDimA = newCoords.a2 - newCoords.a1;                     // calculate dimension a (e.g. width)\r
917                 var targDimB = Math.floor( calcDimA * ratio.b / ratio.a );      // the target dimension b (e.g. height)\r
918                 var targB;                                                                                      // to hold target b (e.g. y value)\r
919                 var targDimA;                                           // to hold target dimension a (e.g. width)\r
920                 var calcDimB = null;                                                            // to hold calculated dimension b (e.g. height)\r
921                 \r
922                 // dump( 'newCoords[0]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');\r
923                                 \r
924                 if( direction.b == 1 ) {                                                        // if travelling in a positive direction\r
925                         // make sure we're not going out of bounds\r
926                         targB = newCoords.b1 + targDimB;\r
927                         if( targB > bounds.max ) {\r
928                                 targB = bounds.max;\r
929                                 calcDimB = targB - newCoords.b1;                        // calcuate dimension b (e.g. height)\r
930                         }\r
931                         \r
932                         newCoords.b2 = targB;\r
933                 } else {                                                                                        // if travelling in a negative direction\r
934                         // make sure we're not going out of bounds\r
935                         targB = newCoords.b2 - targDimB;\r
936                         if( targB < bounds.min ) {\r
937                                 targB = bounds.min;\r
938                                 calcDimB = targB + newCoords.b2;                        // calcuate dimension b (e.g. height)\r
939                         }\r
940                         newCoords.b1 = targB;\r
941                 }\r
942                 \r
943                 // dump( 'newCoords[1]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');\r
944                         \r
945                 // apply the calculated dimensions\r
946                 if( calcDimB != null ) {\r
947                         targDimA = Math.floor( calcDimB * ratio.a / ratio.b );\r
948                         \r
949                         if( direction.a == 1 ) newCoords.a2 = newCoords.a1 + targDimA;\r
950                         else newCoords.a1 = newCoords.a1 = newCoords.a2 - targDimA;\r
951                 }\r
952                 \r
953                 // dump( 'newCoords[2]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');\r
954                         \r
955                 return newCoords;\r
956         },\r
957         \r
958         /**\r
959          * Draws the select area\r
960          * \r
961          * @access private\r
962          * @return void\r
963          */\r
964         drawArea: function( ) { \r
965                 /*\r
966                  * NOTE: I'm not using the Element.setStyle() shortcut as they make it \r
967                  * quite sluggish on Mac based browsers\r
968                  */\r
969                 // dump( 'drawArea        : ' + this.areaCoords.x1 + ',' + this.areaCoords.y1 + ',' + this.areaCoords.x2 + ',' + this.areaCoords.y2 + '\n' );\r
970                 var areaWidth     = this.calcW();\r
971                 var areaHeight    = this.calcH();\r
972                 \r
973                 /*\r
974                  * Calculate all the style strings before we use them, allows reuse & produces quicker\r
975                  * rendering (especially noticable in Mac based browsers)\r
976                  */\r
977                 var px = 'px';\r
978                 var params = [\r
979                         this.areaCoords.x1 + px,        // the left of the selArea\r
980                         this.areaCoords.y1 + px,                // the top of the selArea\r
981                         areaWidth + px,                                 // width of the selArea\r
982                         areaHeight + px,                                        // height of the selArea\r
983                         this.areaCoords.x2 + px,                // bottom of the selArea\r
984                         this.areaCoords.y2 + px,                // right of the selArea\r
985                         (this.img.width - this.areaCoords.x2) + px,     // right edge of selArea\r
986                         (this.img.height - this.areaCoords.y2) + px     // bottom edge of selArea\r
987                 ];\r
988                                 \r
989                 // do the select area\r
990                 var areaStyle                           = this.selArea.style;\r
991                 areaStyle.left                          = params[0];\r
992                 areaStyle.top                           = params[1];\r
993                 areaStyle.width                         = params[2];\r
994                 areaStyle.height                        = params[3];\r
995                                 \r
996                 // position the north, east, south & west handles\r
997                 var horizHandlePos = Math.ceil( (areaWidth - 6) / 2 ) + px;\r
998                 var vertHandlePos = Math.ceil( (areaHeight - 6) / 2 ) + px;\r
999                 \r
1000                 this.handleN.style.left         = horizHandlePos;\r
1001                 this.handleE.style.top          = vertHandlePos;\r
1002                 this.handleS.style.left         = horizHandlePos;\r
1003                 this.handleW.style.top          = vertHandlePos;\r
1004                 \r
1005                 // draw the four overlays\r
1006                 this.north.style.height         = params[1];\r
1007                 \r
1008                 var eastStyle                           = this.east.style;\r
1009                 eastStyle.top                           = params[1];\r
1010                 eastStyle.height                        = params[3];\r
1011                 eastStyle.left                          = params[4];\r
1012             eastStyle.width                             = params[6];\r
1013            \r
1014                 var southStyle                          = this.south.style;\r
1015                 southStyle.top                          = params[5];\r
1016                 southStyle.height                       = params[7];\r
1017            \r
1018             var westStyle                       = this.west.style;\r
1019             westStyle.top                               = params[1];\r
1020             westStyle.height                    = params[3];\r
1021                 westStyle.width                         = params[0];\r
1022                 \r
1023                 // call the draw method on sub classes\r
1024                 this.subDrawArea();\r
1025                 \r
1026                 this.forceReRender();\r
1027         },\r
1028         \r
1029         /**\r
1030          * Force the re-rendering of the selArea element which fixes rendering issues in Safari \r
1031          * & IE PC, especially evident when re-sizing perfectly vertical using any of the south handles\r
1032          * \r
1033          * @access private\r
1034          * @return void\r
1035          */\r
1036         forceReRender: function() {\r
1037                 if( this.isIE || this.isWebKit) {\r
1038                         var n = document.createTextNode(' ');\r
1039                         var d,el,fixEL,i;\r
1040                 \r
1041                         if( this.isIE ) fixEl = this.selArea;\r
1042                         else if( this.isWebKit ) {\r
1043                                 fixEl = document.getElementsByClassName( 'imgCrop_marqueeSouth', this.imgWrap )[0];\r
1044                                 /* we have to be a bit more forceful for Safari, otherwise the the marquee &\r
1045                                  * the south handles still don't move\r
1046                                  */ \r
1047                                 d = Builder.node( 'div', '' );\r
1048                                 d.style.visibility = 'hidden';\r
1049                                 \r
1050                                 var classList = ['SE','S','SW'];\r
1051                                 for( i = 0; i < classList.length; i++ ) {\r
1052                                         el = document.getElementsByClassName( 'imgCrop_handle' + classList[i], this.selArea )[0];\r
1053                                         if( el.childNodes.length ) el.removeChild( el.childNodes[0] );\r
1054                                         el.appendChild(d);\r
1055                                 }\r
1056                         }\r
1057                         fixEl.appendChild(n);\r
1058                         fixEl.removeChild(n);\r
1059                 }\r
1060         },\r
1061         \r
1062         /**\r
1063          * Starts the resize\r
1064          * \r
1065          * @access private\r
1066          * @param obj Event\r
1067          * @return void\r
1068          */\r
1069         startResize: function( e ) {\r
1070                 this.startCoords = this.cloneCoords( this.areaCoords );\r
1071                 \r
1072                 this.resizing = true;\r
1073                 this.resizeHandle = Event.element( e ).classNames().toString().replace(/([^N|NE|E|SE|S|SW|W|NW])+/, '');\r
1074                 // dump( 'this.resizeHandle : ' + this.resizeHandle + '\n' );\r
1075                 Event.stop( e );\r
1076         },\r
1077         \r
1078         /**\r
1079          * Starts the drag\r
1080          * \r
1081          * @access private\r
1082          * @param obj Event\r
1083          * @return void\r
1084          */\r
1085         startDrag: function( e ) {      \r
1086                 this.selArea.show();\r
1087                 this.clickCoords = this.getCurPos( e );\r
1088         \r
1089         this.setAreaCoords( { x1: this.clickCoords.x, y1: this.clickCoords.y, x2: this.clickCoords.x, y2: this.clickCoords.y }, false, false, null );\r
1090         \r
1091         this.dragging = true;\r
1092         this.onDrag( e ); // incase the user just clicks once after already making a selection\r
1093         Event.stop( e );\r
1094         },\r
1095         \r
1096         /**\r
1097          * Gets the current cursor position relative to the image\r
1098          * \r
1099          * @access private\r
1100          * @param obj Event\r
1101          * @return obj x,y pixels of the cursor\r
1102          */\r
1103         getCurPos: function( e ) {\r
1104                 // get the offsets for the wrapper within the document\r
1105                 var el = this.imgWrap, wrapOffsets = Position.cumulativeOffset( el );\r
1106                 // 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\r
1107                 while( el.nodeName != 'BODY' ) {\r
1108                         wrapOffsets[1] -= el.scrollTop  || 0;\r
1109                         wrapOffsets[0] -= el.scrollLeft || 0;\r
1110                         el = el.parentNode;\r
1111             }           \r
1112                 return curPos = { \r
1113                         x: Event.pointerX(e) - wrapOffsets[0],\r
1114                         y: Event.pointerY(e) - wrapOffsets[1]\r
1115                 }\r
1116         },\r
1117         \r
1118         /**\r
1119          * Performs the drag for both resize & inital draw dragging\r
1120          * \r
1121          * @access private\r
1122          * @param obj Event\r
1123          * @return void\r
1124          */\r
1125         onDrag: function( e ) {\r
1126                 if( this.dragging || this.resizing ) {  \r
1127                 \r
1128                         var resizeHandle = null;\r
1129                         var curPos = this.getCurPos( e );                       \r
1130                         var newCoords = this.cloneCoords( this.areaCoords );\r
1131                         var direction = { x: 1, y: 1 };\r
1132                                                 \r
1133                     if( this.dragging ) {\r
1134                         if( curPos.x < this.clickCoords.x ) direction.x = -1;\r
1135                         if( curPos.y < this.clickCoords.y ) direction.y = -1;\r
1136                         \r
1137                                 this.transformCoords( curPos.x, this.clickCoords.x, newCoords, 'x' );\r
1138                                 this.transformCoords( curPos.y, this.clickCoords.y, newCoords, 'y' );\r
1139                         } else if( this.resizing ) {\r
1140                                 resizeHandle = this.resizeHandle;                       \r
1141                                 // do x movements first\r
1142                                 if( resizeHandle.match(/E/) ) {\r
1143                                         // if we're moving an east handle\r
1144                                         this.transformCoords( curPos.x, this.startCoords.x1, newCoords, 'x' );  \r
1145                                         if( curPos.x < this.startCoords.x1 ) direction.x = -1;\r
1146                                 } else if( resizeHandle.match(/W/) ) {\r
1147                                         // if we're moving an west handle\r
1148                                         this.transformCoords( curPos.x, this.startCoords.x2, newCoords, 'x' );\r
1149                                         if( curPos.x < this.startCoords.x2 ) direction.x = -1;\r
1150                                 }\r
1151                                                                         \r
1152                                 // do y movements second\r
1153                                 if( resizeHandle.match(/N/) ) {\r
1154                                         // if we're moving an north handle      \r
1155                                         this.transformCoords( curPos.y, this.startCoords.y2, newCoords, 'y' );\r
1156                                         if( curPos.y < this.startCoords.y2 ) direction.y = -1;\r
1157                                 } else if( resizeHandle.match(/S/) ) {\r
1158                                         // if we're moving an south handle\r
1159                                         this.transformCoords( curPos.y, this.startCoords.y1, newCoords, 'y' );  \r
1160                                         if( curPos.y < this.startCoords.y1 ) direction.y = -1;\r
1161                                 }       \r
1162                                                         \r
1163                         }\r
1164                 \r
1165                         this.setAreaCoords( newCoords, false, e.shiftKey, direction, resizeHandle );\r
1166                         this.drawArea();\r
1167                         Event.stop( e ); // stop the default event (selecting images & text) in Safari & IE PC\r
1168                 }\r
1169         },\r
1170         \r
1171         /**\r
1172          * Applies the appropriate transform to supplied co-ordinates, on the\r
1173          * defined axis, depending on the relationship of the supplied values\r
1174          * \r
1175          * @access private\r
1176          * @param int Current value of pointer\r
1177          * @param int Base value to compare current pointer val to\r
1178          * @param obj Coordinates to apply transformation on x1, x2, y1, y2\r
1179          * @param string Axis to apply transformation on 'x' || 'y'\r
1180          * @return void\r
1181          */\r
1182         transformCoords : function( curVal, baseVal, coords, axis ) {\r
1183                 var newVals = [ curVal, baseVal ];\r
1184                 if( curVal > baseVal ) newVals.reverse();\r
1185                 coords[ axis + '1' ] = newVals[0];\r
1186                 coords[ axis + '2' ] = newVals[1];              \r
1187         },\r
1188         \r
1189         /**\r
1190          * Ends the crop & passes the values of the select area on to the appropriate \r
1191          * callback function on completion of a crop\r
1192          * \r
1193          * @access private\r
1194          * @return void\r
1195          */\r
1196         endCrop : function() {\r
1197                 this.dragging = false;\r
1198                 this.resizing = false;\r
1199                 \r
1200                 this.options.onEndCrop(\r
1201                         this.areaCoords,\r
1202                         {\r
1203                                 width: this.calcW(), \r
1204                                 height: this.calcH() \r
1205                         }\r
1206                 );\r
1207         },\r
1208         \r
1209         /**\r
1210          * Abstract method called on the end of initialization\r
1211          * \r
1212          * @access private\r
1213          * @abstract\r
1214          * @return void\r
1215          */\r
1216         subInitialize: function() {},\r
1217         \r
1218         /**\r
1219          * Abstract method called on the end of drawArea()\r
1220          * \r
1221          * @access private\r
1222          * @abstract\r
1223          * @return void\r
1224          */\r
1225         subDrawArea: function() {}\r
1226 };\r
1227 \r
1228 \r
1229 \r
1230 \r
1231 /**\r
1232  * Extend the Cropper.Img class to allow for presentation of a preview image of the resulting crop,\r
1233  * the option for displayOnInit is always overridden to true when displaying a preview image\r
1234  * \r
1235  * Usage:\r
1236  *      @param obj Image element to attach to\r
1237  *      @param obj Optional options:\r
1238  *              - see Cropper.Img for base options\r
1239  *              \r
1240  *              - previewWrap obj\r
1241  *                      HTML element that will be used as a container for the preview image             \r
1242  */\r
1243 Cropper.ImgWithPreview = Class.create();\r
1244 \r
1245 Object.extend( Object.extend( Cropper.ImgWithPreview.prototype, Cropper.Img.prototype ), {\r
1246         \r
1247         /**\r
1248          * Implements the abstract method from Cropper.Img to initialize preview image settings.\r
1249          * Will only attach a preview image is the previewWrap element is defined and the minWidth\r
1250          * & minHeight options are set.\r
1251          * \r
1252          * @see Croper.Img.subInitialize\r
1253          */\r
1254         subInitialize: function() {\r
1255                 /**\r
1256                  * Whether or not we've attached a preview image\r
1257                  * @var boolean\r
1258                  */\r
1259                 this.hasPreviewImg = false;\r
1260                 if( typeof(this.options.previewWrap) != 'undefined' \r
1261                         && this.options.minWidth > 0 \r
1262                         && this.options.minHeight > 0\r
1263                 ) {\r
1264                         /**\r
1265                          * The preview image wrapper element\r
1266                          * @var obj HTML element\r
1267                          */\r
1268                         this.previewWrap        = $PR( this.options.previewWrap );\r
1269                         /**\r
1270                          * The preview image element\r
1271                          * @var obj HTML IMG element\r
1272                          */\r
1273                         this.previewImg         = this.img.cloneNode( false );\r
1274                         // set the ID of the preview image to be unique\r
1275                         this.previewImg.id      = 'imgCrop_' + this.previewImg.id;\r
1276                         \r
1277                                                 \r
1278                         // set the displayOnInit option to true so we display the select area at the same time as the thumbnail\r
1279                         this.options.displayOnInit = true;\r
1280 \r
1281                         this.hasPreviewImg      = true;\r
1282                         \r
1283                         this.previewWrap.addClassName( 'imgCrop_previewWrap' );\r
1284                         \r
1285                         this.previewWrap.setStyle(\r
1286                          { \r
1287                                 width: this.options.minWidth + 'px',\r
1288                                 height: this.options.minHeight + 'px'\r
1289                          }\r
1290                         );\r
1291                         \r
1292                         this.previewWrap.appendChild( this.previewImg );\r
1293                 }\r
1294         },\r
1295         \r
1296         /**\r
1297          * Implements the abstract method from Cropper.Img to draw the preview image\r
1298          * \r
1299          * @see Croper.Img.subDrawArea\r
1300          */\r
1301         subDrawArea: function() {\r
1302                 if( this.hasPreviewImg ) {\r
1303                         // get the ratio of the select area to the src image\r
1304                         var calcWidth = this.calcW();\r
1305                         var calcHeight = this.calcH();\r
1306                         // ratios for the dimensions of the preview image\r
1307                         var dimRatio = { \r
1308                                 x: this.imgW / calcWidth, \r
1309                                 y: this.imgH / calcHeight \r
1310                         }; \r
1311                         //ratios for the positions within the preview\r
1312                         var posRatio = { \r
1313                                 x: calcWidth / this.options.minWidth, \r
1314                                 y: calcHeight / this.options.minHeight \r
1315                         };\r
1316                         \r
1317                         // setting the positions in an obj before apply styles for rendering speed increase\r
1318                         var calcPos     = {\r
1319                                 w: Math.ceil( this.options.minWidth * dimRatio.x ) + 'px',\r
1320                                 h: Math.ceil( this.options.minHeight * dimRatio.y ) + 'px',\r
1321                                 x: '-' + Math.ceil( this.areaCoords.x1 / posRatio.x )  + 'px',\r
1322                                 y: '-' + Math.ceil( this.areaCoords.y1 / posRatio.y ) + 'px'\r
1323                         }\r
1324                         \r
1325                         var previewStyle        = this.previewImg.style;\r
1326                         previewStyle.width      = calcPos.w;\r
1327                         previewStyle.height     = calcPos.h;\r
1328                         previewStyle.left       = calcPos.x;\r
1329                         previewStyle.top        = calcPos.y;\r
1330                 }\r
1331         }\r
1332         \r
1333 });\r