1 /**
  2  *	Image Map Editor (imgmap) - in-browser imagemap editor
  3  *	Copyright (C) 2006 - 2008 Adam Maschek (adam.maschek @ gmail.com)
  4  *	
  5  *	This program is free software; you can redistribute it and/or
  6  *	modify it under the terms of the GNU General Public License
  7  *	as published by the Free Software Foundation; either version 2
  8  *	of the License, or (at your option) any later version.
  9  *	
 10  *	This program is distributed in the hope that it will be useful,
 11  *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13  *	GNU General Public License for more details.
 14  *	
 15  *	You should have received a copy of the GNU General Public License
 16  *	along with this program; if not, write to the Free Software
 17  *	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 18  */
 19 /**
 20  *	@fileoverview
 21  *	Online Image Map Editor - main script file.
 22  *	This is the main script file of the Online Image Map Editor.
 23  * 
 24  *	TODO:
 25  *	-scriptload race condition fix
 26  *	-destroy/cleanup function ?
 27  *	-testing highlighter
 28  *	-cursor area_mousemove in opera not refreshing quite well - bug reported
 29  *	-get rid of memo array
 30  *	-highlight which control point is edited in html or form mode   
 31  *	-more comments, especially on config vars
 32  *	-make function names more logical
 33  *	- dumpconfig   
 34  *	-prepare for bad input /poly not properly closed?
 35  *	-prepare for % values in coords  
 36  *	-prepare for default shape http://www.w3.org/TR/html4/struct/objects.html#edef-AREA
 37  *	 
 38  *	@date	26-02-2007 2:24:50
 39  *	@author	Adam Maschek (adam.maschek(at)gmail.com)
 40  *	@copyright
 41  *	@version 2.2
 42  *	 
 43  */
 44 /*jslint browser: true, newcap: false, white: false, onevar: false, plusplus: false, eqeqeq: false, nomen: false */
 45 /*global imgmapStrings:true, window:false, G_vmlCanvasManager:false, air:false, imgmap_spawnObjects:true */
 46 /**
 47  *	@author	Adam Maschek
 48  *	@constructor
 49  *	@param config	The config object. 
 50  */
 51 function imgmap(config) {
 52 
 53 	/** Version string of imgmap */
 54 	this.version = "2.2";
 55 	
 56 	/** Build date of imgmap */
 57 	this.buildDate = "2009/07/26 16:02";
 58 	
 59 	/** Sequential build number of imgmap */
 60 	this.buildNumber = "106";
 61 	
 62 	/** Config object of the imgmap instance */
 63 	this.config = {};
 64 	
 65 	/** Status flag to indicate current drawing mode */
 66 	this.is_drawing = 0;
 67 	
 68 	/**	Array to hold language strings */
 69 	this.strings   = [];
 70 	
 71 	/** Helper array for some drawing operations */
 72 	this.memory    = [];
 73 	
 74 	/**	Array to hold reference to all areas (canvases) */
 75 	this.areas     = [];
 76 	
 77 	/**	Array to hold last log entries */
 78 	this.logStore  = [];
 79 
 80 	/**	Associative array to hold bound event handlers */
 81 	this.eventHandlers  = {};
 82 	
 83 	this.currentid = 0;
 84 	this.draggedId  = null;
 85 	this.selectedId = null;
 86 	this.nextShape = 'rect';
 87 
 88 	/** possible values: 0 - edit, 1 - preview */
 89 	this.viewmode  = 0;
 90 	
 91 	/** array of dynamically loaded javascripts */
 92 	this.loadedScripts = [];
 93 	this.isLoaded   = false;
 94 	this.cntReloads = 0;
 95 	
 96 	/**	holds the name of the actively edited map, use getMapName to read it */
 97 	this.mapname    = '';
 98 	
 99 	/**	holds the id of the actively edited map, use getMapIdto read it */
100 	this.mapid      = '';
101 	
102 	/** watermark to attach to output */
103 	this.waterMark  = '<!-- Created by Online Image Map Editor (http://www.maschek.hu/imagemap/index) -->';
104 	
105 	/** global scale of areas (1-normal, 2-doubled, 0.5-half, etc.) */
106 	this.globalscale = 1;
107 
108 	/** is_drawing draw mode constant */
109 	this.DM_RECTANGLE_DRAW          = 1;
110 	/** is_drawing draw mode constant */
111 	this.DM_RECTANGLE_MOVE          = 11;
112 	/** is_drawing draw mode constant */
113 	this.DM_RECTANGLE_RESIZE_TOP    = 12;
114 	/** is_drawing draw mode constant */
115 	this.DM_RECTANGLE_RESIZE_RIGHT  = 13;
116 	/** is_drawing draw mode constant */
117 	this.DM_RECTANGLE_RESIZE_BOTTOM = 14;
118 	/** is_drawing draw mode constant */
119 	this.DM_RECTANGLE_RESIZE_LEFT   = 15;
120 	
121 	/** is_drawing draw mode constant */
122 	this.DM_SQUARE_DRAW             = 2;
123 	/** is_drawing draw mode constant */
124 	this.DM_SQUARE_MOVE             = 21;
125 	/** is_drawing draw mode constant */
126 	this.DM_SQUARE_RESIZE_TOP       = 22;
127 	/** is_drawing draw mode constant */
128 	this.DM_SQUARE_RESIZE_RIGHT     = 23;
129 	/** is_drawing draw mode constant */
130 	this.DM_SQUARE_RESIZE_BOTTOM    = 24;
131 	/** is_drawing draw mode constant */
132 	this.DM_SQUARE_RESIZE_LEFT      = 25;
133 	
134 	/** is_drawing draw mode constant */
135 	this.DM_POLYGON_DRAW            = 3;
136 	/** is_drawing draw mode constant */
137 	this.DM_POLYGON_LASTDRAW        = 30;
138 	/** is_drawing draw mode constant */
139 	this.DM_POLYGON_MOVE            = 31;
140 
141 	/** is_drawing draw mode constant */
142 	this.DM_BEZIER_DRAW             = 4;
143 	/** is_drawing draw mode constant */
144 	this.DM_BEZIER_LASTDRAW         = 40;
145 	/** is_drawing draw mode constant */
146 	this.DM_BEZIER_MOVE             = 41;
147 
148 	//set some config defaults below
149 	
150 	/**
151 	 *	Mode of operation
152 	 *	possible values:
153 	 *	editor - classical editor,
154 	 *	editor2 - dreamweaver style editor,
155 	 *	highlighter - map highlighter, will spawn imgmap instances for each map found in the current page
156 	 *	highlighter_spawn - internal mode after spawning imgmap objects
157 	 */
158 	this.config.mode     = "editor";
159 	
160 	this.config.baseroot    = '';
161 	this.config.lang        = '';
162 	this.config.defaultLang = 'en';
163 	this.config.loglevel    = 0;
164 	this.config.custom_callbacks = {};//possible values: see below!
165 	
166 	/**	Callback events that you can handle in your GUI. */
167 	this.event_types        = [
168 		'onModeChanged',
169 		'onHtmlChanged',
170 		'onAddArea',
171 		'onRemoveArea',
172 		'onDrawArea', 
173 		'onResizeArea',
174 		'onRelaxArea',
175 		'onFocusArea',
176 		'onBlurArea',
177 		'onMoveArea',
178 		'onSelectRow',
179 		'onLoadImage', 
180 		'onSetMap',
181 		'onGetMap',
182 		'onSelectArea',
183 		'onDblClickArea',
184 		'onStatusMessage',
185 		'onAreaChanged'];
186 
187 	//default color values
188 	this.config.CL_DRAW_BOX        = '#E32636';
189 	this.config.CL_DRAW_SHAPE      = '#d00';
190 	this.config.CL_DRAW_BG         = '#fff';
191 	this.config.CL_NORM_BOX        = '#E32636';
192 	this.config.CL_NORM_SHAPE      = '#d00';
193 	this.config.CL_NORM_BG         = '#fff';
194 	this.config.CL_HIGHLIGHT_BOX   = '#E32636';
195 	this.config.CL_HIGHLIGHT_SHAPE = '#d00';
196 	this.config.CL_HIGHLIGHT_BG    = '#fff';
197 	this.config.CL_KNOB            = '#555';
198 
199 	this.config.bounding_box       = true;
200 	this.config.label              = '%n';
201 	//the format string of the area labels - possible values: %n - number, %c - coords, %h - href, %a - alt, %t - title
202 	
203 	this.config.label_class        = 'imgmap_label';
204 	//the css class to apply on labels
205 	this.config.label_style        = 'font: bold 10px Arial';
206 	//this.config.label_style        = 'font-weight: bold; font-size: 10px; font-family: Arial; color: #964';
207 	//the css style(s) to apply on labels
208 	
209 	this.config.hint               = '#%n %h';
210 	//the format string of the area mouseover hints - possible values: %n - number, %c - coords, %h - href, %a - alt, %t - title
211 
212 	this.config.draw_opacity       = '35';
213 	//the opacity value of the area while drawing, moving or resizing - possible values 0 - 100 or range "(x)-y"	
214 	this.config.norm_opacity       = '50';
215 	//the opacity value of the area while relaxed - possible values 0 - 100	or range "(x)-y"
216 	this.config.highlight_opacity  = '70';
217 	//the opacity value of the area while highlighted - possible values 0 - 100 or range "(x)-y"
218 	this.config.cursor_default     = 'crosshair';		//auto/pointer
219 	//the css cursor while hovering over the image
220 	
221 	//browser sniff
222 	var ua = navigator.userAgent;
223 	this.isMSIE    = (navigator.appName == "Microsoft Internet Explorer");
224 	this.isMSIE5   = this.isMSIE && (ua.indexOf('MSIE 5')   != -1);
225 	this.isMSIE5_0 = this.isMSIE && (ua.indexOf('MSIE 5.0') != -1);
226 	this.isMSIE7   = this.isMSIE && (ua.indexOf('MSIE 7')   != -1);
227 	this.isGecko   = ua.indexOf('Gecko')  != -1;
228 	this.isSafari  = ua.indexOf('Safari') != -1;
229 	this.isOpera   = (typeof window.opera != 'undefined');
230 	
231 	this.setup(config);
232 }
233 
234 
235 /**
236  *	Return an object given by id or object itself.
237  *	@date	22-02-2007 0:14:50
238  *	@author	Adam Maschek (adam.maschek(at)gmail.com)
239  *	@param	objorid	A DOM object, or id of a DOM object.
240  *	@return	The identified DOM object or null on error.
241  */
242 imgmap.prototype.assignOID = function(objorid) {
243 	try {
244 		if (typeof objorid == 'undefined') {
245 			this.log("Undefined object passed to assignOID.");// Called from: " + arguments.callee.caller, 1);
246 			return null;
247 		}
248 		else if (typeof objorid == 'object') {
249 			return objorid;
250 		}
251 		else if (typeof objorid == 'string') {
252 			return document.getElementById(objorid);
253 		}
254 	}
255 	catch (err) {
256 		this.log("Error in assignOID", 1);
257 	}
258 	return null;
259 };
260 
261 
262 /**
263  *	Main setup function.
264  *	Can be called manually or constructor will call it.
265  *	@date	22-02-2007 0:15:42
266  *	@author	Adam Maschek (adam.maschek(at)gmail.com)
267  *	@param	config	config object
268  *	@return	True if all went ok.
269  */
270 imgmap.prototype.setup = function(config) {
271 	//this.log('setup');
272 
273 	//copy non-default config parameters to this.config
274 	for (var i in config) {
275 		if (config.hasOwnProperty(i)) {
276 			this.config[i] = config[i];
277 		}
278 	}
279 	
280 	//set document event hooks
281 	this.addEvent(document, 'keydown',   this.eventHandlers.doc_keydown = this.doc_keydown.bind(this));
282 	this.addEvent(document, 'keyup',     this.eventHandlers.doc_keyup = this.doc_keyup.bind(this));
283 	this.addEvent(document, 'mousedown', this.eventHandlers.doc_mousedown = this.doc_mousedown.bind(this));
284 	
285 	//set pic_container element - supposedly it already exists in the DOM
286 	if (config && config.pic_container) {
287 		this.pic_container = this.assignOID(config.pic_container);
288 		this.disableSelection(this.pic_container);
289 	}
290 	
291 	if (!this.config.baseroot) {
292 		//search for a base - theoretically there can only be one, but lets search
293 		//for the first non-empty
294 		var bases = document.getElementsByTagName('base');
295 		var base  = '';
296 		for (i=0; i<bases.length; i++) {//i declared earlier
297 			if (bases[i].href) {
298 				base = bases[i].href;
299 				//append slash if missing
300 				if (base.charAt(base.length-1) != '/') {
301 					base+= '/';
302 				}
303 				break;
304 			}
305 		}
306 		//search for scripts
307 		var scripts = document.getElementsByTagName('script');
308 		for (i=0; i<scripts.length; i++) {//i declared earlier
309 			if (scripts[i].src && scripts[i].src.match(/imgmap\w*\.js(\?.*?)?$/)) {
310 				var src = scripts[i].src;
311 				//cut filename part, leave last slash
312 				src = src.substring(0, src.lastIndexOf('/') + 1);
313 				//set final baseroot path
314 				if (base && src.indexOf('://') == -1) {
315 					this.config.baseroot = base + src;
316 				}
317 				else {
318 					this.config.baseroot = src;
319 				}
320 				//exit loop
321 				break;
322 			}
323 		}
324 	}
325 
326 	//load excanvas js - as soon as possible
327 	if (this.isMSIE &&
328 		typeof window.CanvasRenderingContext2D == 'undefined' && typeof G_vmlCanvasManager == 'undefined') { 
329 		this.loadScript(this.config.baseroot + 'excanvas.js');
330 		//alert('loadcanvas');
331 	}
332 	//alert(this.config.baseroot);
333 
334 	//load language js - as soon as possible
335 	if (!this.config.lang) {
336 		this.config.lang = this.detectLanguage();
337 	}
338 	if (typeof imgmapStrings == 'undefined') {
339 		//language file might have already been loaded (ex highlighter mode)
340 		this.loadScript(this.config.baseroot + 'lang_' + this.config.lang + '.js');
341 	}
342 	
343 	//check event hooks
344 	var found, j, le;
345 	for (i in this.config.custom_callbacks) {
346 		if (this.config.custom_callbacks.hasOwnProperty(i)) {
347 			found = false;
348 			for (j = 0, le = this.event_types.length; j < le; j++) {
349 				if (i == this.event_types[j]) {
350 					found = true;
351 					break;
352 				}
353 			}
354 			if (!found) {
355 				this.log("Unknown custom callback: " + i, 1);
356 			}
357 		}
358 	}
359 	
360 	//hook onload event - as late as possible
361 	this.addEvent(window, 'load', this.onLoad.bind(this));
362 	return true;
363 };
364 
365 
366 /**
367  *	currently unused
368  *	@ignore
369  */
370 imgmap.prototype.retryDelayed = function(fn, delay, tries) {
371 	if (typeof fn.tries == 'undefined') {fn.tries = 0;}
372 	//alert(fn.tries+1);
373 	if (fn.tries++ < tries) {
374 		//alert('ss');
375 		window.setTimeout(function() {
376 		fn.apply(this);
377 		}, delay);
378 	}
379 };
380 
381 
382 /**
383  *	EVENT HANDLER: Handle event when the page with scripts is loaded.
384  *	@date	22-02-2007 0:16:22
385  *	@author	Adam Maschek (adam.maschek(at)gmail.com)
386  *	@param	e	The event object.
387  */
388 imgmap.prototype.onLoad = function(e) {
389 	if (this.isLoaded) {return true;}
390 	var _this = this;
391 	//this.log('readystate: ' +  document.readyState);
392 	if (typeof imgmapStrings == 'undefined') {
393 		if (this.cntReloads++ < 5) {
394 			//this.retryDelayed(_this.onLoad(), 1000, 3);
395 			window.setTimeout(function () {_this.onLoad(e);} ,1200);
396 			this.log('Delaying onload (language ' + this.config.lang + ' not loaded, try: ' + this.cntReloads + ')');
397 			return false;
398 		}
399 		else if (this.config.lang != this.config.defaultLang && this.config.defaultLang != 'en') {
400 			this.log('Falling back to default language: ' + this.config.defaultLang);
401 			this.cntReloads = 0;
402 			this.config.lang = this.config.defaultLang;
403 			this.loadScript(this.config.baseroot + 'lang_' + this.config.lang + '.js');
404 			window.setTimeout(function () {_this.onLoad(e);} ,1200);
405 			return false;
406 		}
407 		else if (this.config.lang != 'en') {
408 			this.log('Falling back to english language');
409 			this.cntReloads = 0;
410 			this.config.lang = 'en';
411 			this.loadScript(this.config.baseroot + 'lang_' + this.config.lang + '.js');
412 			window.setTimeout(function () {_this.onLoad(e);} ,1200);
413 			return false;
414 		}
415 	}
416 	//else
417 	try {
418 		this.loadStrings(imgmapStrings);
419 	}
420 	catch (err) {
421 		this.log("Unable to load language strings", 1);
422 	}
423 	
424 	//check if ExplorerCanvas correctly loaded - detect if browser supports canvas
425 	//alert(typeof G_vmlCanvasManager + this.isMSIE + typeof window.CanvasRenderingContext2D);
426 	if (this.isMSIE) {
427 		//alert('cccc');
428 		//alert(typeof G_vmlCanvasManager);
429 		if (typeof window.CanvasRenderingContext2D == 'undefined' && typeof G_vmlCanvasManager == 'undefined') {
430 			//alert('bbb');
431 			/*
432 			if (this.cntReloads++ < 5) {
433 				var _this = this;
434 				//this.retryDelayed(_this.onLoad(), 1000, 3);
435 				window.setTimeout(function () {
436 					_this.onLoad(e);
437 					}
438 					,1000
439 					);
440 				//alert('aaa');
441 				this.log('Delaying onload (excanvas not loaded, try: ' + this.cntReloads + ')');
442 				return false;
443 			}
444 			*/
445 			this.log(this.strings.ERR_EXCANVAS_LOAD, 2);//critical error
446 		}
447 	}
448 	
449 	if (this.config.mode == 'highlighter') {
450 		//call global scope function
451 		imgmap_spawnObjects(this.config);	
452 	}
453 	this.isLoaded = true;
454 	return true;
455 };
456 
457 
458 /**
459  *	Attach new 'evt' event handler 'callback' to 'obj'
460  *	@date	24-02-2007 21:16:20
461  *	@param	obj	The object on which the handler is defined.
462  *	@param	evt	The name of the event, like mousedown.
463  *	@param	callback	The callback function (named if you want it to be removed).
464  */
465 imgmap.prototype.addEvent = function(obj, evt, callback) {
466 	if (obj.attachEvent) {
467 		//Microsoft style registration model
468 		return obj.attachEvent("on" + evt, callback);
469 	}
470 	else if (obj.addEventListener) {
471 		//W3C style model
472 		obj.addEventListener(evt, callback, false);
473 		return true;
474 	}
475 	else {
476 		obj['on' + evt] = callback;
477 	}
478 };
479 
480 
481 /**
482  *	Detach 'evt' event handled by 'callback' from 'obj' object.
483  *	Callback must be a non anonymous function, see eventHandlers.
484  *	@see	#eventHandlers 
485  *	Example: myimgmap.removeEvent(myimgmap.pic, 'mousedown', myimgmap.eventHandlers.img_mousedown); 
486  *	@date	24-11-2007 15:22:17
487  *	@param	obj	The object on which the handler is defined.
488  *	@param	evt	The name of the event, like mousedown.
489  *	@param	callback	The named callback function.
490  */
491 imgmap.prototype.removeEvent = function(obj, evt, callback) {
492 	if (obj.detachEvent) {
493 		//Microsoft style detach model
494 		return obj.detachEvent("on" + evt, callback);
495 	}
496 	else if (obj.removeEventListener) {
497 		//W3C style model
498 		obj.removeEventListener(evt, callback, false);
499 		return true;
500 	}
501 	else {
502 		obj['on' + evt] = null;
503 	}
504 };
505 
506 
507 /**
508  *	We need this because load events for scripts function slightly differently.
509  *	@link	http://dean.edwards.name/weblog/2006/06/again/
510  *	@author	Adam Maschek (adam.maschek(at)gmail.com)
511  *	@date	24-03-2007 11:02:21
512  */
513 imgmap.prototype.addLoadEvent = function(obj, callback) {
514 	if (obj.attachEvent) {
515 		//Microsoft style registration model
516 		return obj.attachEvent("onreadystatechange", callback);
517 	}
518 	else if (obj.addEventListener) {
519 		//W3C style registration model
520 		obj.addEventListener('load', callback, false);
521 		return true;
522 	}
523 	else {
524 		obj.onload = callback;
525 	}
526 };
527 
528 
529 /**
530  *	Include another js script into the current document.
531  *	@date	22-02-2007 0:17:04
532  *	@author	Adam Maschek (adam.maschek(at)gmail.com)
533  *	@param	url	The url of the script we want to load.
534  *	@see	#script_load
535  *	@see	#addLoadEvent
536  */
537 imgmap.prototype.loadScript = function(url) {
538 	if (url === '') {return false;}
539 	if (this.loadedScripts[url] == 1) {return true;}//script already loaded
540 	this.log('Loading script: ' + url);
541 	//we might need this someday for safari?
542 	//var temp = '<script language="javascript" type="text/javascript" src="' + url + '"></script>';
543 	//document.write(temp);
544 	try {
545 		var head = document.getElementsByTagName('head')[0];
546 		var temp = document.createElement('SCRIPT');
547 		temp.setAttribute('language', 'javascript');
548 		temp.setAttribute('type', 'text/javascript');
549 		temp.setAttribute('src', url);
550 		//temp.setAttribute('defer', true);
551 		head.appendChild(temp);
552 		this.addLoadEvent(temp, this.script_load.bind(this));
553 	}
554 	catch (err) {
555 		this.log('Error loading script: ' + url);
556 	}
557 	return true;
558 };
559 
560 
561 /**
562  *	EVENT HANDLER: Event handler of external script loaded.
563  *	@param	e	The event object. 
564  */
565 imgmap.prototype.script_load = function(e) {
566 	var obj = (this.isMSIE) ? window.event.srcElement : e.currentTarget;
567 	var url = obj.src;
568 	var complete = false;
569 	//alert(url);
570 	if (typeof obj.readyState != 'undefined') {
571 		//explorer
572 		if (obj.readyState == 'complete') {
573 			complete = true;
574 		}
575 	}
576 	else {
577 		//other browsers?
578 		complete = true;
579 	}
580 	if (complete) {
581 		this.loadedScripts[url] = 1;
582 		this.log('Loaded script: ' + url);
583 		return true;
584 	}
585 };
586 
587 
588 /**
589  *	Load strings from a key:value object to the prototype strings array.
590  *	@author adam
591  *	@date	2007
592  *	@param	obj	Javascript object that holds key:value pairs.
593  */
594 imgmap.prototype.loadStrings = function(obj) {
595 	for (var key in obj) {
596 		if (obj.hasOwnProperty(key)) {
597 			this.strings[key] = obj[key];
598 		}
599 	}
600 };
601 
602 
603 /**
604  *	This function is to load a given img url to the pic_container.
605  *	
606  *	Loading an image will clear all current maps.
607  *	@see	#useImage 
608  *	@param img The imageurl or object to load (if object, function will get url, and do a recall)
609  *	@param imgw The width we want to force on the image	(optional)
610  *	@param imgh The height we want to force on the image (optional)
611  *	@returns True on success 
612  */
613 imgmap.prototype.loadImage = function(img, imgw, imgh) {
614 	//test for container
615 	if (typeof this.pic_container == 'undefined') {
616 		this.log('You must have pic_container defined to use loadImage!', 2);
617 		return false;
618 	}
619 	//wipe all
620 	this.removeAllAreas();
621 	//reset scale
622 	this.globalscale = 1;
623 	this.fireEvent('onHtmlChanged', '');//empty
624 	if (!this._getLastArea()) {
625 		//init with one new area if there was none editable
626 		if (this.config.mode != "editor2") {this.addNewArea();}
627 	}
628 	if (typeof img == 'string') {
629 		//there is an image given with url to load
630 		if (typeof this.pic == 'undefined') {
631 			this.pic = document.createElement('IMG');
632 			this.pic_container.appendChild(this.pic);
633 			//event handler hooking - only at the first load
634 			this.addEvent(this.pic, 'mousedown', this.eventHandlers.img_mousedown = this.img_mousedown.bind(this));
635 			this.addEvent(this.pic, 'mouseup',   this.eventHandlers.img_mouseup = this.img_mouseup.bind(this));
636 			this.addEvent(this.pic, 'mousemove', this.eventHandlers.img_mousemove = this.img_mousemove.bind(this));
637 			this.pic.style.cursor = this.config.cursor_default;
638 		}
639 		//img ='../../'+img;
640 		this.log('Loading image: ' + img, 0);
641 		//calculate timestamp to bypass browser cache mechanism
642 		var q = '?';
643 		if (img.indexOf('?') > -1) {
644 			q = '&';
645 		}
646 		this.pic.src = img + q + (new Date().getTime());
647 		if (imgw && imgw > 0) {this.pic.setAttribute('width',  imgw);}
648 		if (imgh && imgh > 0) {this.pic.setAttribute('height', imgh);}
649 		this.fireEvent('onLoadImage', this.pic);
650 		return true;
651 	}
652 	else if (typeof img == 'object') {
653 		//we have to use the src of the image object
654 		var src = img.src; //img.getAttribute('src');
655 		if (src === '' && img.getAttribute('mce_src') !== '') {
656 			//if it is a tinymce object, it has no src but mce_src attribute!
657 			src = img.getAttribute('mce_src');
658 		}
659 		else if (src === '' && img.getAttribute('_fcksavedurl') !== '') {
660 			//if it is an fck object, it might have only _fcksavedurl attribute!
661 			src = img.getAttribute('_fcksavedurl');
662 		}
663 		// Get the displayed dimensions of the image
664 		if (!imgw) {
665 			imgw = img.clientWidth;
666 		}
667 		if (!imgh) {
668 			imgh = img.clientHeight;
669 		}
670 		//recurse, this time with the url string
671 		return this.loadImage(src, imgw, imgh);
672 	}
673 };
674 
675 
676 /**
677  *	We use this when there is an existing image object we want to handle with imgmap.
678  *	Mainly used in highlighter mode.
679  *	@author	adam
680  *	@see	#loadImage
681  *	@see	#imgmap_spawnObjects
682  *	@date	2007
683  *	@param	img	DOM object or id of image we want to use.
684  */
685 imgmap.prototype.useImage = function(img) {
686 	//wipe all
687 	this.removeAllAreas();
688 	if (!this._getLastArea()) {
689 		//init with one new area if there was none editable
690 		if (this.config.mode != "editor2") {this.addNewArea();}
691 	}
692 	img = this.assignOID(img);
693 	if (typeof img == 'object') {
694 		if (typeof this.pic != 'undefined') {
695 			//remove previous handlers
696 			this.removeEvent(this.pic, 'mousedown', this.eventHandlers.img_mousedown);
697 			this.removeEvent(this.pic, 'mouseup',   this.eventHandlers.img_mouseup);
698 			this.removeEvent(this.pic, 'mousemove', this.eventHandlers.img_mousemove);
699 			this.pic.style.cursor = '';
700 		}
701 		this.pic = img;
702 		//hook event handlers
703 		this.addEvent(this.pic, 'mousedown', this.eventHandlers.img_mousedown = this.img_mousedown.bind(this));
704 		this.addEvent(this.pic, 'mouseup',   this.eventHandlers.img_mouseup = this.img_mouseup.bind(this));
705 		this.addEvent(this.pic, 'mousemove', this.eventHandlers.img_mousemove = this.img_mousemove.bind(this));
706 		this.pic.style.cursor = this.config.cursor_default;
707 		
708 		if (this.pic.parentNode.className == 'pic_container') {
709 			//use existing container
710 			this.pic_container = this.pic.parentNode;
711 		}
712 		else {
713 			//dynamically create container
714 			this.pic_container = document.createElement("div");
715 			this.pic_container.className = 'pic_container';
716 			this.pic.parentNode.insertBefore(this.pic_container, this.pic);
717 			//ref: If the node already exists it is removed from current parent node, then added to new parent node.
718 			this.pic_container.appendChild(this.pic);
719 		}
720 
721 		this.fireEvent('onLoadImage', this.pic);
722 		return true;
723 	}
724 };
725 
726 
727 /**
728  *	Fires custom hook onStatusMessage, passing the status string.
729  *	Use this to update your GUI. 
730  *	@author	Adam Maschek (adam.maschek(at)gmail.com)
731  *	@date	26-07-2008 13:22:54
732  *	@param	str	The status string
733  */
734 imgmap.prototype.statusMessage = function(str) {
735 	this.fireEvent('onStatusMessage', str);
736 };
737 
738 
739 /**
740  *	Adds basic logging functionality using firebug console object if available.
741  *	Also tries to use AIR introspector if available. 
742  *	@date	20-02-2007 17:55:18
743  *	@author	Adam Maschek (adam.maschek(at)gmail.com)
744  *	@param	obj	The object or string you want to debug/echo.
745  *	@level	level	The log level, 0 being the smallest issue.  
746  */
747 imgmap.prototype.log = function(obj, level) {
748 	if (level === '' || typeof level == 'undefined') {level = 0;}
749 	if (this.config.loglevel != -1 && level >= this.config.loglevel) {
750 		this.logStore.push({level: level, obj: obj});
751 	}
752 	if (typeof console == 'object') {
753 		console.log(obj);
754 	}
755 	else if (this.isOpera) {
756 		opera.postError(level + ': ' + obj);
757 	}
758 	else if (typeof air == 'object') {
759 		//we are inside AIR
760 		if (typeof air.Introspector == 'object') {
761 			air.Introspector.Console.log(obj);
762 		}
763 	else {
764 			air.trace(obj);
765 		}
766 	}
767 	else {
768 		if (level > 1) {
769 			//alert(level + ': ' + obj);
770 			//dump with all pevious errors:
771 			var msg = '';
772 			for (var i=0, le = this.logStore.length; i<le; i++) {
773 				msg+= this.logStore[i].level + ': ' + this.logStore[i].obj + "\n";
774 			}
775 			alert(msg);
776 		}
777 		else {
778 			window.defaultStatus = (level + ': ' + obj);
779 		}
780 	}
781 };
782 
783 
784 /**
785  *	Produces the image map HTML output with the defined areas.
786  *	Invokes getMapInnerHTML. 
787  *	@author	Adam Maschek (adam.maschek(at)gmail.com)
788  *	@date	2006-06-06 15:10:27
789  *	@param	flags	Currently ony 'noscale' used to prevent scaling of coordinates in preview mode. 
790  *	@return The generated html code. 
791  */
792 imgmap.prototype.getMapHTML = function(flags) {
793 	var html = '<map id="'+this.getMapId()+'" name="'+this.getMapName()+'">' + this.getMapInnerHTML(flags) + this.waterMark + '</map>';
794 	this.fireEvent('onGetMap', html);
795 	//alert(html);
796 	return html;
797 };
798 
799 
800 /**
801  *	Get the map areas part only of the current imagemap.
802  *	@see	#getMapHTML 
803  *	@author	adam
804  *	@param	flags	Currently ony 'noscale' used to prevent scaling of coordinates in preview mode. 
805  *	@return	The generated map code without the map wrapper.  
806  */
807 imgmap.prototype.getMapInnerHTML = function(flags) {
808 	var html, coords;
809 	html = '';
810 	//foreach area properties
811 	for (var i=0, le = this.areas.length; i<le; i++) {
812 		if (this.areas[i]) {
813 			if (this.areas[i].shape && this.areas[i].shape != 'undefined') {
814 				coords = this.areas[i].lastInput;
815 				if (flags && flags.match(/noscale/)) {
816 					//for preview use real coordinates, not scaled
817 					var cs = coords.split(',');
818 					for (var j=0, le2 = cs.length; j<le2; j++) {
819 						cs[j] = Math.round(cs[j] * this.globalscale);
820 					}
821 					coords = cs.join(',');
822 				}
823 				html+= '<area shape="' + this.areas[i].shape + '"' +
824 					' alt="' + this.areas[i].aalt + '"' +
825 					' title="' + this.areas[i].atitle + '"' +
826 					' coords="' + coords + '"' +
827 					' href="' +	this.areas[i].ahref + '"' +
828 					' target="' + this.areas[i].atarget + '" />';
829 			}
830 		}
831 	}
832 	//alert(html);
833 	return html;
834 };
835 
836 
837 /**
838  *	Get the map name of the current imagemap.
839  *	If doesnt exist, nor map id, generate a new name based on timestamp.
840  *	The most portable solution is to use the same value for id and name.
841  *	This also conforms the HTML 5 specification, that says:
842  *	"If the id  attribute is also specified, both attributes must have the same value."   
843  *	@link	http://www.w3.org/html/wg/html5/#the-map-element
844  *	@author	adam
845  *	@see	#getMapId
846  *	@return The name of the map.
847  */
848 imgmap.prototype.getMapName = function() {
849 	if (this.mapname === '') {
850 		if (this.mapid !== '') {return this.mapid;}
851 		var now = new Date();
852 		this.mapname = 'imgmap' + now.getFullYear() + (now.getMonth()+1) + now.getDate() + now.getHours() + now.getMinutes() + now.getSeconds();
853 	}
854 	return this.mapname;
855 };
856 
857 
858 /**
859  *	Get the map id of the current imagemap.
860  *	If doesnt exist, use map name.
861  *	@author	adam
862  *	@see	#getMapName
863  *	@return	The id of the map.    
864  */
865 imgmap.prototype.getMapId = function() {
866 	if (this.mapid === '') {
867 		this.mapid = this.getMapName();
868 	}
869 	return this.mapid;
870 };
871 
872 
873 /**
874  *	Convert wild shape names to normal ones.
875  *	@date	25-12-2008 19:27:06
876  *	@param	shape	The name of the shape to convert.
877  *	@return	The normalized shape name, rect as default.
878  */
879 imgmap.prototype._normShape = function(shape) {
880 	if (!shape) {return 'rect';}
881 	shape = this.trim(shape).toLowerCase();
882 	if (shape.substring(0, 4) == 'rect') {return 'rect';}
883 	if (shape.substring(0, 4) == 'circ') {return 'circle';}
884 	if (shape.substring(0, 4) == 'poly') {return 'poly';}
885 	return 'rect';
886 };
887 
888 
889 /**
890  *	Try to normalize coordinates that came from:
891  *	1. html textarea
892  *	2. user input in the active area's input field
893  *	3. from the html source in case of plugins or highlighter
894  *	Example of inputs that need to be handled:
895  *		035,035 075,062
896  *		150,217, 190,257, 150,297,110,257
897  *	@author	adam
898  *	@param	coords	The coordinates in a string.
899  *	@param	shape	The shape of the object (rect, circle, poly, bezier1).
900  *	@param	flag	Flags that modify the operation. (fromcircle, frompoly, fromrect, preserve)
901  *	@returns The normalized coordinates. 
902  */
903 imgmap.prototype._normCoords = function(coords, shape, flag) {
904 	//function level var declarations
905 	var i;//generic cycle counter
906 	var sx;//smallest x
907 	var sy;//smallest y
908 	var gx;//greatest x
909 	var gy;//greatest y
910 	var temp, le;
911 	
912 	//console.log('normcoords: ' + coords + ' - ' + shape + ' - ' + flag);
913 	coords = this.trim(coords);
914 	if (coords === '') {return '';}
915 	var oldcoords = coords;
916 	//replace some general junk
917 	coords = coords.replace(/(\d)(\D)+(\d)/g, "$1,$3");
918 	coords = coords.replace(/,\D+(\d)/g, ",$1");//cut leading junk
919 	coords = coords.replace(/,0+(\d)/g, ",$1");//cut leading zeros
920 	coords = coords.replace(/(\d)(\D)+,/g, "$1,");
921 	coords = coords.replace(/^\D+(\d)/g, "$1");//cut leading junk
922 	coords = coords.replace(/^0+(\d)/g, "$1");//cut leading zeros
923 	coords = coords.replace(/(\d)(\D)+$/g, "$1");//cut trailing junk
924 	//console.log('>'+coords + ' - ' + shape + ' - ' + flag);
925 	//now fix other issues
926 	var parts = coords.split(',');
927 	if (shape == 'rect') {
928 		if (flag == 'fromcircle') {
929 			var r = parts[2];
930 			parts[0] = parts[0] - r;
931 			parts[1] = parts[1] - r;
932 			parts[2] = parseInt(parts[0], 10) + 2 * r;
933 			parts[3] = parseInt(parts[1], 10) + 2 * r;
934 		}
935 		else if (flag == 'frompoly') {
936 			sx = parseInt(parts[0], 10); gx = parseInt(parts[0], 10);
937 			sy = parseInt(parts[1], 10); gy = parseInt(parts[1], 10);
938 			for (i=0, le = parts.length; i<le; i++) {
939 				if (i % 2 === 0 && parseInt(parts[i], 10) < sx) {
940 					sx = parseInt(parts[i], 10);}
941 				if (i % 2 === 1 && parseInt(parts[i], 10) < sy) {
942 					sy = parseInt(parts[i], 10);}
943 				if (i % 2 === 0 && parseInt(parts[i], 10) > gx) {
944 					gx = parseInt(parts[i], 10);}
945 				if (i % 2 === 1 && parseInt(parts[i], 10) > gy) {
946 					gy = parseInt(parts[i], 10);}
947 				//console.log(sx+","+sy+","+gx+","+gy);
948 			}
949 			parts[0] = sx; parts[1] = sy;
950 			parts[2] = gx; parts[3] = gy;
951 		}
952 		if (!(parseInt(parts[1], 10) >= 0)) {parts[1] = parts[0];}
953 		if (!(parseInt(parts[2], 10) >= 0)) {parts[2] = parseInt(parts[0], 10) + 10;}
954 		if (!(parseInt(parts[3], 10) >= 0)) {parts[3] = parseInt(parts[1], 10) + 10;}
955 		if (parseInt(parts[0], 10) > parseInt(parts[2], 10)) {
956 			temp = parts[0];
957 			parts[0] = parts[2];
958 			parts[2] = temp;
959 		}
960 		if (parseInt(parts[1], 10) > parseInt(parts[3], 10)) {
961 			temp = parts[1];
962 			parts[1] = parts[3];
963 			parts[3] = temp;
964 		}
965 		coords = parts[0]+","+parts[1]+","+parts[2]+","+parts[3];
966 		//console.log(coords);
967 	}
968 	else if (shape == 'circle') {
969 		if (flag == 'fromrect') {
970 			sx = parseInt(parts[0], 10); gx = parseInt(parts[2], 10);
971 			sy = parseInt(parts[1], 10); gy = parseInt(parts[3], 10);
972 			//use smaller side
973 			parts[2] = (gx - sx < gy - sy) ? gx - sx : gy - sy;
974 			parts[2] = Math.floor(parts[2] / 2);//radius
975 			parts[0] = sx + parts[2];
976 			parts[1] = sy + parts[2];
977 		}
978 		else if (flag == 'frompoly') {
979 			sx = parseInt(parts[0], 10); gx = parseInt(parts[0], 10);
980 			sy = parseInt(parts[1], 10); gy = parseInt(parts[1], 10);
981 			for (i=0, le = parts.length; i<le; i++) {
982 				if (i % 2 === 0 && parseInt(parts[i], 10) < sx) {
983 					sx = parseInt(parts[i], 10);}
984 				if (i % 2 === 1 && parseInt(parts[i], 10) < sy) {
985 					sy = parseInt(parts[i], 10);}
986 				if (i % 2 === 0 && parseInt(parts[i], 10) > gx) {
987 					gx = parseInt(parts[i], 10);}
988 				if (i % 2 === 1 && parseInt(parts[i], 10) > gy) {
989 					gy = parseInt(parts[i], 10);}
990 				//console.log(sx+","+sy+","+gx+","+gy);
991 			}
992 			//use smaller side
993 			parts[2] = (gx - sx < gy - sy) ? gx - sx : gy - sy;
994 			parts[2] = Math.floor(parts[2] / 2);//radius
995 			parts[0] = sx + parts[2];
996 			parts[1] = sy + parts[2];
997 		}
998 		if (!(parseInt(parts[1], 10) > 0)) {parts[1] = parts[0];}
999 		if (!(parseInt(parts[2], 10) > 0)) {parts[2] = 10;}
1000 		coords = parts[0]+","+parts[1]+","+parts[2];
1001 	}
1002 	else if (shape == 'poly') {
1003 		if (flag == 'fromrect') {
1004 			parts[4] = parts[2];
1005 			parts[5] = parts[3];
1006 			parts[2] = parts[0];
1007 			parts[6] = parts[4];
1008 			parts[7] = parts[1];
1009 		}
1010 		else if (flag == 'fromcircle') {
1011 			//@url http://www.pixelwit.com/blog/2007/06/29/basic-circle-drawing-actionscript/
1012 			var centerX = parseInt(parts[0], 10);
1013 			var centerY = parseInt(parts[1], 10);
1014 			var radius  = parseInt(parts[2], 10);
1015 			var j = 0;
1016 			parts[j++] = centerX + radius;
1017 			parts[j++] = centerY;
1018 			var sides = 60;//constant = sides the fake circle will have
1019 			for (i=0; i<=sides; i++) {
1020 				var pointRatio = i/sides;
1021 				var xSteps = Math.cos(pointRatio*2*Math.PI);
1022 				var ySteps = Math.sin(pointRatio*2*Math.PI);
1023 				var pointX = centerX + xSteps * radius;
1024 				var pointY = centerY + ySteps * radius;
1025 				parts[j++] = Math.round(pointX);
1026 				parts[j++] = Math.round(pointY);
1027 			}
1028 			//console.log(parts);
1029 		}
1030 		coords = parts.join(',');
1031 	}
1032 	else if (shape == 'bezier1') {
1033 		coords = parts.join(',');
1034 	}
1035 	if (flag == 'preserve' && oldcoords != coords) {
1036 		//return original and throw error
1037 		//throw "invalid coords";
1038 		return oldcoords;
1039 	}
1040 	return coords;
1041 };
1042 
1043 
1044 /**
1045  *	Sets the coordinates according to the given HTML map code or DOM object.
1046  *	@author	Adam Maschek (adam.maschek(at)gmail.com)
1047  *	@date	2006-06-07 11:47:16
1048  *	@param	map	DOM object or string of a map you want to apply.
1049  *	@return	True on success 
1050  */   
1051 imgmap.prototype.setMapHTML = function(map) {
1052 	if (this.viewmode === 1) {return;}//exit if preview mode
1053 	
1054 	this.fireEvent('onSetMap', map);
1055 	//this.log(map);
1056 	//remove all areas
1057 	this.removeAllAreas();
1058 	//console.log(this.areas);
1059 	
1060 	var oMap;
1061 	if (typeof map == 'string') {
1062 		var oHolder = document.createElement('DIV');
1063 		oHolder.innerHTML = map;
1064 		oMap = oHolder.firstChild;
1065 	}
1066 	else if (typeof map == 'object') {
1067 		oMap = map;
1068 	}
1069 	if (!oMap || oMap.nodeName.toLowerCase() !== 'map') {return false;}
1070 	this.mapname = oMap.name;
1071 	this.mapid   = oMap.id;
1072 	var newareas = oMap.getElementsByTagName('area');
1073 	var shape, coords, href, alt, title, target, id;
1074 	for (var i=0, le = newareas.length; i<le; i++) {
1075 		shape = coords = href = alt = title = target = '';
1076 		
1077 		id = this.addNewArea();//btw id == this.currentid, just this form is a bit clearer
1078 
1079 		shape = this._normShape(newareas[i].getAttribute('shape', 2));
1080 		
1081 		this.initArea(id, shape);
1082 		
1083 		if (newareas[i].getAttribute('coords', 2)) {
1084 			//normalize coords
1085 			coords = this._normCoords(newareas[i].getAttribute('coords', 2), shape);
1086 			this.areas[id].lastInput = coords;
1087 			//for area this one will be set in recalculate
1088 		}
1089 
1090 		href = newareas[i].getAttribute('href', 2);
1091 		// FCKeditor stored url to prevent mangling from the browser.
1092 		var sSavedUrl = newareas[i].getAttribute( '_fcksavedurl' );
1093 		if (sSavedUrl) {
1094 			href = sSavedUrl;
1095 		}
1096 		if (href) {
1097 			this.areas[id].ahref = href;
1098 		}
1099 		
1100 		alt = newareas[i].getAttribute('alt');
1101 		if (alt) {
1102 			this.areas[id].aalt = alt;
1103 		}
1104 		
1105 		title = newareas[i].getAttribute('title');
1106 		if (!title) {title = alt;}
1107 		if (title) {
1108 			this.areas[id].atitle = title;
1109 		}
1110 
1111 		target = newareas[i].getAttribute('target');
1112 		if (target) {target = target.toLowerCase();}
1113 //		if (target == '') target = '_self';
1114 		this.areas[id].atarget = target;
1115 		
1116 		this._recalculate(id, coords);//contains repaint
1117 		this.relaxArea(id);
1118 		
1119 		this.fireEvent('onAreaChanged', this.areas[id]);
1120 		
1121 	}//end for areas
1122 	this.fireEvent('onHtmlChanged', this.getMapHTML());
1123 	return true;
1124 };
1125 
1126 
1127 /**
1128  *	Preview image with imagemap applied.
1129  *	@author	Adam Maschek (adam.maschek(at)gmail.com)
1130  *	@date	2006-06-06 14:51:01
1131  *	@url	http://www.quirksmode.org/bugreports/archives/2005/03/Usemap_attribute_wrongly_case_sensitive.html
1132  *	@return	False on error, 0 when switched to edit mode, 1 when switched to preview mode
1133  */
1134 imgmap.prototype.togglePreview = function() {
1135 	var i, le;//generic cycle counter
1136 	
1137 	if (!this.pic) {return false;}//exit if pic is undefined
1138 	
1139 	//dynamically create preview container
1140 	if (!this.preview) {
1141 		this.preview = document.createElement('DIV');
1142 		this.preview.style.display = 'none';
1143 		this.pic_container.appendChild(this.preview);
1144 	}
1145 	
1146 	if (this.viewmode === 0) {
1147 		//hide canvas elements and labels
1148 		for (i = 0, le = this.areas.length; i < le; i++) {
1149 			if (this.areas[i]) {
1150 				this.areas[i].style.display = 'none';
1151 				if (this.areas[i].label) {this.areas[i].label.style.display = 'none';}
1152 			}
1153 		}
1154 		//activate image map
1155 		this.preview.innerHTML = this.getMapHTML('noscale');
1156 		this.pic.setAttribute('border', '0', 0);
1157 		this.pic.setAttribute('usemap', '#' + this.mapname, 0);
1158 		this.pic.style.cursor  = 'auto';
1159 		this.viewmode = 1;
1160 		this.statusMessage(this.strings.PREVIEW_MODE);
1161 	}
1162 	else {
1163 		//show canvas elements
1164 		for (i = 0, le = this.areas.length; i < le; i++) {
1165 			if (this.areas[i]) {
1166 				this.areas[i].style.display = '';
1167 				if (this.areas[i].label && this.config.label) {this.areas[i].label.style.display = '';}
1168 			}
1169 		}
1170 		//clear image map
1171 		this.preview.innerHTML = '';
1172 		this.pic.style.cursor  = this.config.cursor_default;
1173 		this.pic.removeAttribute('usemap', 0);
1174 		this.viewmode = 0;
1175 		this.statusMessage(this.strings.DESIGN_MODE);
1176 		this.is_drawing = 0;
1177 	}
1178 	this.fireEvent('onModeChanged', this.viewmode);
1179 	return this.viewmode;
1180 };
1181 
1182 
1183 /**
1184  *	Adds a new area. It will later become a canvas.
1185  *	GUI should use the onAddArea callback to act accordingly.
1186  *	@author	Adam Maschek (adam.maschek(at)gmail.com)
1187  *	@date	2006-06-06 16:49:25
1188  *	@see	#initArea
1189  */
1190 imgmap.prototype.addNewArea = function() {
1191 		if (this.viewmode === 1) {return;}//exit if preview mode
1192 		var lastarea = this._getLastArea();
1193 		var id = (lastarea) ? lastarea.aid + 1 : 0;
1194 		//alert(id);
1195 		
1196 		//insert new possibly? unknown area (will be initialized at mousedown)
1197 		this.areas[id] = document.createElement('DIV');
1198 		this.areas[id].id        = this.mapname + 'area' + id;
1199 		this.areas[id].aid       = id;
1200 		this.areas[id].shape     = "undefined";
1201 		
1202 		this.currentid = id;
1203 		this.fireEvent('onAddArea', id);
1204 		return id;
1205 };
1206 
1207 
1208 /**
1209  *	Initialize a new area.
1210  *	Create the canvas, initialize it.
1211  *	Reset area parameters.
1212  *	@param	id	The id of the area (already existing with undefined shape)
1213  *	@param	shape	The shape the area will have (rect, circle, poly, bezier1)
1214  */
1215 imgmap.prototype.initArea = function(id, shape) {
1216 	if (!this.areas[id]) {return false;}//if all was erased, return
1217 	//remove preinited dummy div or already placed canvas
1218 	if (this.areas[id].parentNode) {this.areas[id].parentNode.removeChild(this.areas[id]);}
1219 	if (this.areas[id].label) {this.areas[id].label.parentNode.removeChild(this.areas[id].label);}
1220 	this.areas[id] = null;
1221 	//create CANVAS node
1222 	this.areas[id] = document.createElement('CANVAS');
1223 	this.pic_container.appendChild(this.areas[id]);
1224 	this.pic_container.style.position = 'relative';
1225 	//alert('init' + typeof G_vmlCanvasManager);
1226 	if (typeof G_vmlCanvasManager != "undefined") {
1227 		//override CANVAS with VML object
1228 		this.areas[id] = G_vmlCanvasManager.initElement(this.areas[id]);
1229 		//this.areas[id] = this.pic.parentNode.lastChild;
1230 	}
1231 	this.areas[id].id        = this.mapname + 'area' + id;
1232 	this.areas[id].aid       = id;
1233 	this.areas[id].shape     = shape;
1234 	this.areas[id].ahref     = '';
1235 	this.areas[id].atitle    = '';
1236 	this.areas[id].aalt      = '';
1237 	this.areas[id].atarget   = ''; // '_self';
1238 	this.areas[id].style.position = 'absolute';
1239 	this.areas[id].style.top      = this.pic.offsetTop  + 'px';
1240 	this.areas[id].style.left     = this.pic.offsetLeft + 'px';
1241 	this._setopacity(this.areas[id], this.config.CL_DRAW_BG, this.config.draw_opacity);
1242 	//hook event handlers
1243 	this.areas[id].ondblclick  = this.area_dblclick.bind(this);
1244 	this.areas[id].onmousedown = this.area_mousedown.bind(this);
1245 	this.areas[id].onmouseup   = this.area_mouseup.bind(this);
1246 	this.areas[id].onmousemove = this.area_mousemove.bind(this);
1247 	this.areas[id].onmouseover = this.area_mouseover.bind(this);
1248 	this.areas[id].onmouseout  = this.area_mouseout.bind(this);
1249 	//initialize memory object
1250 	this.memory[id] = {};
1251 	this.memory[id].downx   = 0;
1252 	this.memory[id].downy   = 0;
1253 	this.memory[id].left    = 0;
1254 	this.memory[id].top     = 0;
1255 	this.memory[id].width   = 0;
1256 	this.memory[id].height  = 0;
1257 	this.memory[id].xpoints = [];
1258 	this.memory[id].ypoints = [];
1259 	//create label node
1260 	this.areas[id].label = document.createElement('DIV');
1261 	this.pic_container.appendChild(this.areas[id].label);
1262 	this.areas[id].label.className      = this.config.label_class;
1263 	this.assignCSS(this.areas[id].label,  this.config.label_style);
1264 	this.areas[id].label.style.position = 'absolute';
1265 };
1266 
1267 
1268 /**
1269  *	Resets area border and opacity to a normal state after drawing.
1270  *	@author	Adam Maschek (adam.maschek(at)gmail.com)
1271  *	@date	15-02-2007 22:07:28
1272  *	@param	id	The id of the area. 
1273  *	@see	#relaxAllAreas
1274  */
1275 imgmap.prototype.relaxArea = function(id) {
1276 	if (!this.areas[id]) {return;}
1277 	this.fireEvent('onRelaxArea', id);
1278 	this._setBorder(id, 'NORM');
1279 	this._setopacity(this.areas[id], this.config.CL_NORM_BG, this.config.norm_opacity);
1280 };
1281 
1282 
1283 /**
1284  *	Resets area border and opacity of all areas.
1285  *	Calls relaxArea on each of them. 
1286  *	@author	Adam Maschek (adam.maschek(at)gmail.com)
1287  *	@date	23-04-2007 23:31:09
1288  *	@see	#relaxArea
1289  */
1290 imgmap.prototype.relaxAllAreas = function() {
1291 	for (var i=0, le = this.areas.length; i<le; i++) {
1292 		if (this.areas[i]) {
1293 			this.relaxArea(i);
1294 		}
1295 	}
1296 };
1297 
1298 
1299 /**
1300  *	Set border of a given area according to style flag.
1301  *	Possible values of style: NORM, HIGHLIGHT, DRAW.
1302  *	Non-rectangle shapes wont get a border if config.bounding_box is false.
1303  *	@date	26-12-2008 22:34:41
1304  *	@param	id	The id of the area to set the border on.
1305  *	@param	style	Coloring style (NORM, HIGHLIGHT, DRAW), see relevant colors in config.
1306  *	@since	2.1
1307  */
1308 imgmap.prototype._setBorder = function(id, style) {
1309 	if (this.areas[id].shape == 'rect' || this.config.bounding_box) {
1310 		this.areas[id].style.borderWidth = '1px';
1311 		this.areas[id].style.borderStyle = (style == 'DRAW' ? 'dotted' : 'solid');
1312 		this.areas[id].style.borderColor = this.config['CL_' + style + '_' + (this.areas[id].shape == 'rect' ? 'SHAPE' : 'BOX')];
1313 	}
1314 	else {
1315 		//clear border
1316 		this.areas[id].style.border = '';
1317 	}
1318 };
1319 
1320 
1321 /**
1322  *	Set opacity of area to the given percentage, as well as set the background color.
1323  *	If percentage contains a dash(-), the setting of the opacity will be gradual. 
1324  *	@param	area	The area object.
1325  *	@param	bgcolor	New background color
1326  *	@param	pct		Percentage of the opacity.   
1327  */
1328 imgmap.prototype._setopacity = function(area, bgcolor, pct) {
1329 	if (bgcolor) {area.style.backgroundColor = bgcolor;}
1330 	if (pct && typeof pct == 'string' && pct.match(/^\d*\-\d+$/)) {
1331 		//gradual fade
1332 		var parts = pct.split('-');
1333 		if (typeof parts[0] != 'undefined') {
1334 			//set initial opacity
1335 			parts[0] = parseInt(parts[0], 10);
1336 			this._setopacity(area, bgcolor, parts[0]);
1337 		}
1338 		if (typeof parts[1] != 'undefined') {
1339 			parts[1] = parseInt(parts[1], 10);
1340 			var curr = this._getopacity(area);
1341 			//this.log('curr: '+curr);
1342 			var _this = this;
1343 			var diff = Math.round(parts[1] - curr);
1344 			if (diff > 5) {
1345 				window.setTimeout(function () {_this._setopacity(area, null, '-'+parts[1]);}, 20);
1346 				pct = 1*curr + 5;
1347 			}
1348 			else if (diff < -3) {
1349 				window.setTimeout(function () {_this._setopacity(area, null, '-'+parts[1]);}, 20);
1350 				pct = 1*curr - 3;
1351 			}
1352 			else {
1353 				//final set
1354 				pct = parts[1];
1355 			}
1356 		}
1357 	}
1358 	if (!isNaN(pct)) {
1359 		pct = Math.round(parseInt(pct, 10));
1360 		//this.log('set ('+area.aid+'): ' + pct, 1);
1361 		area.style.opacity = pct / 100;
1362 		area.style.filter  = 'alpha(opacity='+pct+')';
1363 	}
1364 };
1365 
1366 
1367 /**
1368  *	Get the currently set opacity of a given area.
1369  *	@author	adam
1370  *	@param	area	The area (canvas) you want to get opacity info from.
1371  *	@return	Opacity value in a range of 0-100.   
1372  */
1373 imgmap.prototype._getopacity = function(area) {
1374 	if (area.style.opacity <= 1) {
1375 		return area.style.opacity * 100;
1376 	}
1377 	if (area.style.filter) {
1378 		//alpha(opacity=NaN)
1379 		return parseInt(area.style.filter.replace(/alpha\(opacity\=([^\)]*)\)/ig, "$1"), 10);	
1380 	}
1381 	return 100;//default opacity
1382 };
1383 
1384 
1385 /**
1386  *	Removes the area marked by id.
1387  *	removeAllAreas will indicate a mass flag so that the output HTML will only be updated at 
1388  *	the end of the operation.
1389  *	Callback will call the GUI code to remove GUI elements. 
1390  *	@author	Adam Maschek (adam.maschek(at)gmail.com)
1391  *	@date	11-02-2007 20:40:58
1392  *	@param	id	The id of the area to remove.
1393  *	@param	mass	Flag to indicate skipping the call of onHtmlChanged callback
1394  *	@see	#removeAllAreas
1395  */
1396 imgmap.prototype.removeArea = function(id, mass) {
1397 	if (this.viewmode === 1) {return;}//exit if preview mode
1398 	if (id === null || typeof id == "undefined") {return;}//exit if no id given
1399 
1400 	try {
1401 		//remove area and label
1402 		//explicitly set some values to null to avoid IE circular reference memleak 
1403 		this.areas[id].label.parentNode.removeChild(this.areas[id].label);
1404 		this.areas[id].parentNode.removeChild(this.areas[id]);
1405 		this.areas[id].label.className = null;
1406 		//this.areas[id].label.style = null;
1407 		//console.log(this.areas[id].label);
1408 		this.areas[id].label       = null;
1409 		this.areas[id].onmouseover = null;
1410 		this.areas[id].onmouseout  = null;
1411 		this.areas[id].onmouseup   = null;
1412 		this.areas[id].onmousedown = null;
1413 		this.areas[id].onmousemove = null;
1414 //		console.log(this.areas[id].label);
1415 		
1416 	}
1417 	catch (err) {
1418 		//alert('noparent');
1419 	}
1420 	this.areas[id] = null;
1421 	this.fireEvent('onRemoveArea', id);
1422 	//update grand html
1423 	if (!mass) {this.fireEvent('onHtmlChanged', this.getMapHTML());}
1424 };
1425 
1426 
1427 /**
1428  *	Removes all areas.
1429  *	Will call removeArea on all areas. 
1430  *	@author	Adam Maschek (adam.maschek(at)gmail.com)
1431  *	@date	2006-06-07 11:55:34
1432  *	@see	#removeArea
1433  */
1434 imgmap.prototype.removeAllAreas = function() {
1435 	for (var i = 0, le = this.areas.length; i < le; i++) {
1436 		if (this.areas[i]) {
1437 			this.removeArea(i, true);
1438 		}
1439 	}
1440 	//only call this at the end, use mass param above to avoid calling it in process
1441 	this.fireEvent('onHtmlChanged', this.getMapHTML());
1442 };
1443 
1444 
1445 /**
1446  *	Scales all areas.
1447  *	Will store scale parameter in globalscale property.
1448  *	This is needed to know how to draw new areas on an already scaled canvas.  
1449  *	@author	adam
1450  *	@date	02-11-2008 14:13:14
1451  *	@param scale	Scale factor (1-original, 0.5-half, 2-double, etc.)
1452  */
1453 imgmap.prototype.scaleAllAreas = function(scale) {
1454 	var rscale = 1;//relative scale
1455 	try {
1456 		rscale = scale / this.globalscale;
1457 	}
1458 	catch (err) {
1459 		this.log("Invalid (global)scale", 1);
1460 	}
1461 	//console.log('gscale: '+this.globalscale);
1462 	//console.log('scale: '+scale);
1463 	//console.log('rscale: '+rscale);
1464 
1465 	this.globalscale = scale;
1466 	for (var i = 0, le = this.areas.length; i < le; i++) {
1467 		if (this.areas[i] && this.areas[i].shape != 'undefined') {
1468 			this.scaleArea(i, rscale);
1469 		}
1470 	}
1471 };
1472 
1473 
1474 /**
1475  *	Scales one area.
1476  *	@author	adam
1477  *	@date	02-11-2008 14:13:14
1478  *	@param rscale	Relative scale factor (1-keep, 0.5-half, 2-double, etc.)
1479  */
1480 imgmap.prototype.scaleArea = function(id, rscale) {
1481 	
1482 	//set position and new dimensions
1483 	this.areas[id].style.top  = parseInt(this.areas[id].style.top, 10) * rscale + 'px';
1484 	this.areas[id].style.left = parseInt(this.areas[id].style.left, 10) * rscale + 'px';
1485 	this.setAreaSize(id, this.areas[id].width * rscale, this.areas[id].height * rscale);
1486 	
1487 	//handle polygon/bezier coordinates scaling
1488 	if (this.areas[id].shape == 'poly' || this.areas[id].shape == 'bezier1') {
1489 		for (var i=0, le = this.areas[id].xpoints.length; i<le; i++) {
1490 			this.areas[id].xpoints[i]*= rscale;
1491 			this.areas[id].ypoints[i]*= rscale;
1492 		}
1493 	}
1494 
1495 	this._repaint(this.areas[id], this.config.CL_NORM_SHAPE);
1496 	this._updatecoords(id);
1497 };
1498 
1499 
1500 /**
1501  *	Put label in the top left corner according to label config.
1502  *	By default it will contain the number of the area (area.aid) 
1503  *	@param	id	The id of the area to add label to.
1504  */
1505 imgmap.prototype._putlabel = function(id) {
1506 	if (this.viewmode === 1) {return;}//exit if preview mode
1507 	if (!this.areas[id].label) {return;}//not yet inited
1508 	try {
1509 		if (!this.config.label) {
1510 			this.areas[id].label.innerHTML     = '';
1511 			this.areas[id].label.style.display = 'none';
1512 		}
1513 		else {
1514 			this.areas[id].label.style.display = '';
1515 			var label = this.config.label;
1516 			label = label.replace(/%n/g, String(id));
1517 			label = label.replace(/%c/g, String(this.areas[id].lastInput));
1518 			label = label.replace(/%h/g, String(this.areas[id].ahref));
1519 			label = label.replace(/%a/g, String(this.areas[id].aalt));
1520 			label = label.replace(/%t/g, String(this.areas[id].atitle));
1521 			this.areas[id].label.innerHTML = label;
1522 		}
1523 		//align to the top left corner
1524 		this.areas[id].label.style.top  = this.areas[id].style.top;
1525 		this.areas[id].label.style.left = this.areas[id].style.left;
1526 	}
1527 	catch (err) {
1528 		this.log("Error putting label", 1);
1529 	}
1530 };
1531 
1532 
1533 /**
1534  *	Set area title and alt (for IE) according to the hint configuration.
1535  *	This will show up in the usual yellow box when you hover over with the mouse.
1536  *	@param	id	The id of the area to set hint at.
1537  */
1538 imgmap.prototype._puthint = function(id) {
1539 	try {
1540 		if (!this.config.hint) {
1541 			this.areas[id].title = '';
1542 			this.areas[id].alt   = '';
1543 		}
1544 		else {
1545 			var hint = this.config.hint;
1546 			hint = hint.replace(/%n/g, String(id));
1547 			hint = hint.replace(/%c/g, String(this.areas[id].lastInput));
1548 			hint = hint.replace(/%h/g, String(this.areas[id].ahref));
1549 			hint = hint.replace(/%a/g, String(this.areas[id].aalt));
1550 			hint = hint.replace(/%t/g, String(this.areas[id].atitle));
1551 			this.areas[id].title = hint;
1552 			this.areas[id].alt   = hint;
1553 		}
1554 	}
1555 	catch (err) {
1556 		this.log("Error putting hint", 1);
1557 	}
1558 };
1559 
1560 
1561 /**
1562  *	Will call repaint on all areas.
1563  *	Useful when you change labeling or hint config on the GUI.
1564  *	@see #_repaint
1565  */
1566 imgmap.prototype._repaintAll = function() {
1567 	for (var i=0, le = this.areas.length; i<le; i++) {
1568 		if (this.areas[i]) {
1569 			this._repaint(this.areas[i], this.config.CL_NORM_SHAPE);
1570 		}
1571 	}
1572 };
1573 
1574 
1575 /**
1576  *	Repaints the actual canvas content.
1577  *	This is the only canvas drawing magic that is happening.
1578  *	In fact rectangles will not have any canvas content, just a normal css border.
1579  *	After repainting the canvas, it will call putlabel and puthint methods.
1580  *	@param	area	The area object.
1581  *	@param	color	Color of the line to draw on the canvas.
1582  *	@param	x	Only used for polygons/beziers as the newest control point x.
1583  *	@param	y	Only used for polygons/beziers as the newest control point y.
1584  */  
1585 imgmap.prototype._repaint = function(area, color, x, y) {
1586 	var ctx;//canvas context
1587 	var width, height, left, top;//canvas properties
1588 	var i, le;//loop counter
1589 	if (area.shape == 'circle') {
1590 		width  = parseInt(area.style.width, 10);
1591 		var radius = Math.floor(width/2) - 1;
1592 		//get canvas context
1593 		//alert(area.tagName);
1594 		ctx = area.getContext("2d");
1595 		//clear canvas
1596 		ctx.clearRect(0, 0, width, width);
1597 		//draw circle
1598 		ctx.beginPath();
1599 		ctx.strokeStyle = color;
1600 		ctx.arc(radius, radius, radius, 0, Math.PI*2, 0);
1601 		ctx.stroke();
1602 		ctx.closePath();
1603 		//draw center
1604 		ctx.strokeStyle = this.config.CL_KNOB;
1605 		ctx.strokeRect(radius, radius, 1, 1);
1606 		//put label
1607 		this._putlabel(area.aid);
1608 		this._puthint(area.aid);
1609 	}
1610 	else if (area.shape == 'rect') {
1611 		//put label
1612 		this._putlabel(area.aid);
1613 		this._puthint(area.aid);
1614 	}
1615 	else if (area.shape == 'poly') {
1616 		width  =  parseInt(area.style.width, 10);
1617 		height =  parseInt(area.style.height, 10);
1618 		left   =  parseInt(area.style.left, 10);
1619 		top    =  parseInt(area.style.top, 10);
1620 		if (area.xpoints) {
1621 			//get canvas context
1622 			ctx = area.getContext("2d");
1623 			//clear canvas
1624 			ctx.clearRect(0, 0, width, height);
1625 			//draw polygon
1626 			ctx.beginPath();
1627 			ctx.strokeStyle = color;
1628 			ctx.moveTo(area.xpoints[0] - left, area.ypoints[0] - top);
1629 			for (i = 1, le = area.xpoints.length; i < le; i++) {
1630 				ctx.lineTo(area.xpoints[i] - left , area.ypoints[i] - top);
1631 			}
1632 			if (this.is_drawing == this.DM_POLYGON_DRAW || this.is_drawing == this.DM_POLYGON_LASTDRAW) {
1633 				//only draw to the current position if not moving
1634 				ctx.lineTo(x - left - 5 , y - top - 5);
1635 			}
1636 			ctx.lineTo(area.xpoints[0] - left , area.ypoints[0] - top);
1637 			ctx.stroke();
1638 			ctx.closePath();
1639 		}
1640 		//put label
1641 		this._putlabel(area.aid);
1642 		this._puthint(area.aid);
1643 	}
1644 	else if (area.shape == 'bezier1') {
1645 		width  =  parseInt(area.style.width, 10);
1646 		height =  parseInt(area.style.height, 10);
1647 		left   =  parseInt(area.style.left, 10);
1648 		top    =  parseInt(area.style.top, 10);
1649 		if (area.xpoints) {
1650 			//get canvas context
1651 			ctx = area.getContext("2d");
1652 			//clear canvas
1653 			ctx.clearRect(0, 0, width, height);
1654 			//draw bezier1 (every second point is control point)
1655 			ctx.beginPath();
1656 			ctx.strokeStyle = color;
1657 			//move to the beginning position
1658 			ctx.moveTo(area.xpoints[0] - left, area.ypoints[0] - top);
1659 			//draw previous points - use every second point only
1660 			for (i = 2, le = area.xpoints.length; i < le; i+= 2) {
1661 				ctx.quadraticCurveTo(area.xpoints[i-1] - left, area.ypoints[i-1] - top, area.xpoints[i] - left, area.ypoints[i] - top);
1662 			}
1663 			if (this.is_drawing == this.DM_BEZIER_DRAW || this.is_drawing == this.DM_BEZIER_LASTDRAW) {
1664 				//only draw to the current position if not moving
1665 				if (area.xpoints.length % 2 === 0 && area.xpoints.length > 1) {
1666 					//drawing point - draw a curve to it using the previous control point
1667 					ctx.quadraticCurveTo(area.xpoints[area.xpoints.length - 1] - left - 5 , area.ypoints[area.ypoints.length - 1] - top - 5, x - left - 5 , y - top - 5);
1668 				}
1669 				else {
1670 					//control point - simply draw a line to it
1671 					ctx.lineTo(x - left - 5 , y - top - 5);
1672 				}
1673 			}
1674 			//close area by drawing a line to the first point
1675 			ctx.lineTo(area.xpoints[0] - left , area.ypoints[0] - top);
1676 			ctx.stroke();
1677 			ctx.closePath();
1678 		}
1679 		//put label
1680 		this._putlabel(area.aid);
1681 		this._puthint(area.aid);
1682 	}
1683 };
1684 
1685 
1686 /**
1687  *	Updates Area coordinates.
1688  *	Called when needed, eg. on mousemove, mousedown.
1689  *	Also updates html container value (thru hook).
1690  *	Calls callback onAreaChanged and onHtmlChanged so that GUI can follow.
1691  *	This is an important hook to your GUI.  
1692  *	Uses globalscale to scale real coordinates to area coordinates. 
1693  *	@date	2006.10.24. 22:39:27
1694  *	@author	Adam Maschek (adam.maschek(at)gmail.com)
1695  *	@param	id	The id of the area. 
1696  */
1697 imgmap.prototype._updatecoords = function(id) {
1698 
1699 	var left   = Math.round(parseInt(this.areas[id].style.left, 10) / this.globalscale);
1700 	var top    = Math.round(parseInt(this.areas[id].style.top, 10) / this.globalscale);
1701 	var height = Math.round(parseInt(this.areas[id].style.height, 10) / this.globalscale);
1702 	var width  = Math.round(parseInt(this.areas[id].style.width, 10) / this.globalscale);
1703 
1704 	var value = '';
1705 	if (this.areas[id].shape == 'rect') {
1706 		value = left + ',' + top + ',' + (left + width) + ',' + (top + height);
1707 		this.areas[id].lastInput = value;
1708 	}
1709 	else if (this.areas[id].shape == 'circle') {
1710 		var radius = Math.floor(width/2) - 1;
1711 		value = (left + radius) + ',' +	(top + radius) + ',' + radius;
1712 		this.areas[id].lastInput = value;
1713 	}
1714 	else if (this.areas[id].shape == 'poly' || this.areas[id].shape == 'bezier1') {
1715 		if (this.areas[id].xpoints) {
1716 			for (var i=0, le = this.areas[id].xpoints.length; i<le; i++) {
1717 				value+= Math.round(this.areas[id].xpoints[i] / this.globalscale) + ',' +
1718 						Math.round(this.areas[id].ypoints[i] / this.globalscale) + ',';
1719 			}
1720 			value = value.substring(0, value.length - 1);
1721 		}
1722 		this.areas[id].lastInput = value;
1723 	}
1724 
1725 	this.fireEvent('onAreaChanged', this.areas[id]);
1726 	this.fireEvent('onHtmlChanged', this.getMapHTML());
1727 };
1728 
1729 
1730 /**
1731  *	Updates the visual representation of the area with the given id according
1732  *	to the new coordinates that typically come from an input on the GUI.
1733  *	Uses globalscale to scale area coordinates to real coordinates. 
1734  *	@date	2006.10.24. 22:46:55
1735  *	@author	Adam Maschek (adam.maschek(at)gmail.com)
1736  *	@param	id	The id of the area.
1737  *	@param	coords	The new coords, they will be normalized.
1738  */
1739 imgmap.prototype._recalculate = function(id, coords) {
1740 	try {
1741 		if (coords) {
1742 			coords = this._normCoords(coords, this.areas[id].shape, 'preserve');
1743 		}
1744 		else {
1745 			coords = this.areas[id].lastInput || '' ;
1746 		}
1747 	
1748 		var parts   = coords.split(',');
1749 		if (this.areas[id].shape == 'rect') {
1750 			if (parts.length != 4 ||
1751 				parseInt(parts[0], 10) > parseInt(parts[2], 10) ||
1752 				parseInt(parts[1], 10) > parseInt(parts[3], 10)) {throw "invalid coords";}
1753 			this.areas[id].style.left   = this.globalscale * (this.pic.offsetLeft + parseInt(parts[0], 10)) + 'px';
1754 			this.areas[id].style.top    = this.globalscale * (this.pic.offsetTop  + parseInt(parts[1], 10)) + 'px';
1755 			this.setAreaSize(id, this.globalscale * (parts[2] - parts[0]), this.globalscale  * (parts[3] - parts[1]));
1756 			this._repaint(this.areas[id], this.config.CL_NORM_SHAPE);
1757 		}
1758 		else if (this.areas[id].shape == 'circle') {
1759 			if (parts.length != 3 ||
1760 				parseInt(parts[2], 10) < 0) {throw "invalid coords";}
1761 			var width = 2 * (parts[2]);
1762 			//alert(parts[2]);
1763 			//alert(width);
1764 			this.setAreaSize(id, this.globalscale * width, this.globalscale * width);
1765 			this.areas[id].style.left   = this.globalscale * (this.pic.offsetLeft + parseInt(parts[0], 10) - width/2) + 'px';
1766 			this.areas[id].style.top    = this.globalscale * (this.pic.offsetTop  + parseInt(parts[1], 10) - width/2) + 'px';
1767 			this._repaint(this.areas[id], this.config.CL_NORM_SHAPE);
1768 		}
1769 		else if (this.areas[id].shape == 'poly' || this.areas[id].shape == 'bezier1') {
1770 			if (parts.length < 2) {throw "invalid coords";}
1771 			this.areas[id].xpoints = [];
1772 			this.areas[id].ypoints = [];
1773 			for (var i=0, le = parts.length; i<le; i+=2) {
1774 				this.areas[id].xpoints[this.areas[id].xpoints.length]  = this.globalscale * (this.pic.offsetLeft + parseInt(parts[i], 10));
1775 				this.areas[id].ypoints[this.areas[id].ypoints.length]  = this.globalscale * (this.pic.offsetTop  + parseInt(parts[i+1], 10)); 
1776 				this._polygongrow(this.areas[id], this.globalscale * parts[i], this.globalscale * parts[i+1]);
1777 			}
1778 			this._polygonshrink(this.areas[id]);//includes repaint
1779 		}
1780 	}
1781 	catch (err) {
1782 		var msg = (err.message) ? err.message : 'error calculating coordinates';
1783 		this.log(msg, 1);
1784 		this.statusMessage(this.strings.ERR_INVALID_COORDS);
1785 		if (this.areas[id].lastInput) {
1786 			this.fireEvent('onAreaChanged', this.areas[id]);
1787 		}
1788 		this._repaint(this.areas[id], this.config.CL_NORM_SHAPE);
1789 		return;
1790 	}
1791 	//on success update lastInput
1792 	this.areas[id].lastInput = coords;
1793 };
1794 
1795 
1796 /**
1797  *	Grow polygon area to be able to contain the given new coordinates.
1798  *	@author	adam
1799  *	@param	area	The area to grow.
1800  *	@param	newx	The new coordinate x.  
1801  *	@param	newy	The new coordinate y.	 
1802  *	@see	#_polygonshrink 
1803  */
1804 imgmap.prototype._polygongrow = function(area, newx, newy) {
1805 	//this.log('pgrow');
1806 	var xdiff = newx - parseInt(area.style.left, 10);
1807 	var ydiff = newy - parseInt(area.style.top , 10);
1808 	var pad   = 0;//padding on the edges
1809 	var pad2  = 0;//twice the padding
1810 	
1811 	if (newx < parseInt(area.style.left, 10)) {
1812 		area.style.left   = (newx - pad) + 'px';
1813 		this.setAreaSize(area.aid, parseInt(area.style.width, 10) + Math.abs(xdiff) + pad2, null);
1814 	}
1815 	else if (newx > parseInt(area.style.left, 10) + parseInt(area.style.width, 10)) {
1816 		this.setAreaSize(area.aid, newx - parseInt(area.style.left, 10) + pad2, null);
1817 	}
1818 	if (newy < parseInt(area.style.top, 10)) {
1819 		area.style.top    = (newy - pad) + 'px';
1820 		this.setAreaSize(area.aid, null, parseInt(area.style.height, 10) + Math.abs(ydiff) + pad2);
1821 	}
1822 	else if (newy > parseInt(area.style.top, 10) + parseInt(area.style.height, 10)) {
1823 		this.setAreaSize(area.aid, null, newy - parseInt(area.style.top, 10) + pad2);
1824 	}
1825 };
1826 
1827 
1828 /**
1829  *	Shrink the polygon bounding area to the necessary size, by first reducing it
1830  *	to the minimum, and then gradually growing it.
1831  *	We need this because while we were drawing the polygon, it might have expanded
1832  *	the canvas more than needed.
1833  *	Will repaint the area. 
1834  *	@author	adam
1835  *	@param	area	The area to shrink.
1836  *	@see	#_polygongrow 
1837  */
1838 imgmap.prototype._polygonshrink = function(area) {
1839 	//this.log('pshrink');
1840 	area.style.left = (area.xpoints[0]) + 'px';
1841 	area.style.top  = (area.ypoints[0]) + 'px';
1842 	this.setAreaSize(area.aid, 0, 0);
1843 	for (var i=0, le = area.xpoints.length; i<le; i++) {
1844 		this._polygongrow(area, area.xpoints[i], area.ypoints[i]);
1845 	}
1846 	this._repaint(area, this.config.CL_NORM_SHAPE);
1847 };
1848 
1849 
1850 /**
1851  *	EVENT HANDLER: Handles mousemove on the image.
1852  *	This is the main drawing routine.
1853  *	Depending on the current shape, will draw the rect/circle/poly to the new position. 
1854  *	@param	e	The event object.
1855  */
1856 imgmap.prototype.img_mousemove = function(e) {
1857 	//function level var declarations
1858 	var x;
1859 	var y;
1860 	var xdiff;
1861 	var ydiff;
1862 	var diff;
1863 
1864 	if (this.viewmode === 1) {return;}//exit if preview mode
1865 	//event.x is relative to parent element, but page.x is NOT
1866 	//pos coordinates are the same absolute coords, offset coords are relative to parent
1867 	var pos = this._getPos(this.pic);
1868 	x = (this.isMSIE) ? (window.event.x - this.pic.offsetLeft) : (e.pageX - pos.x);
1869 	y = (this.isMSIE) ? (window.event.y - this.pic.offsetTop)  : (e.pageY - pos.y);
1870 	x = x + this.pic_container.scrollLeft;
1871 	y = y + this.pic_container.scrollTop;
1872 	
1873 	
1874 	//this.log(x + ' - ' + y + ': ' + this.memory[this.currentid].downx + ' - ' +this.memory[this.currentid].downy);
1875 	
1876 	//exit if outside image
1877 	if (x<0 || y<0 || x>this.pic.width || y>this.pic.height) {return;}
1878 	
1879 	//old dimensions that need to be updated in this function
1880 	if (this.memory[this.currentid]) {
1881 		var top    = this.memory[this.currentid].top;
1882 		var left   = this.memory[this.currentid].left;
1883 		var height = this.memory[this.currentid].height;
1884 		var width  = this.memory[this.currentid].width;
1885 	}
1886 	
1887 	// Handle shift state for Safari
1888 	// Safari doesn't generate keyboard events for modifiers: http://bugs.webkit.org/show_bug.cgi?id=11696
1889 	if (this.isSafari) {
1890 		if (e.shiftKey) {
1891 			if (this.is_drawing == this.DM_RECTANGLE_DRAW) {
1892 				this.is_drawing = this.DM_SQUARE_DRAW;
1893 				this.statusMessage(this.strings.SQUARE2_DRAW);
1894 			}
1895 		}
1896 		else {
1897 			if (this.is_drawing == this.DM_SQUARE_DRAW && this.areas[this.currentid].shape == 'rect') {
1898 				//not for circle!
1899 				this.is_drawing = this.DM_RECTANGLE_DRAW;
1900 				this.statusMessage(this.strings.RECTANGLE_DRAW);
1901 			}
1902 		}
1903 	}
1904 
1905 	
1906 	if (this.is_drawing == this.DM_RECTANGLE_DRAW) {
1907 		//rectangle mode
1908 		this.fireEvent('onDrawArea', this.currentid);
1909 		xdiff = x - this.memory[this.currentid].downx;
1910 		ydiff = y - this.memory[this.currentid].downy;
1911 		//alert(xdiff);
1912 		this.setAreaSize(this.currentid, Math.abs(xdiff), Math.abs(ydiff));
1913 		if (xdiff < 0) {
1914 			this.areas[this.currentid].style.left = (x + 1) + 'px';
1915 		}
1916 		if (ydiff < 0) {
1917 			this.areas[this.currentid].style.top  = (y + 1) + 'px';
1918 		}
1919 	}
1920 	else if (this.is_drawing == this.DM_SQUARE_DRAW) {
1921 		//square mode - align to shorter side
1922 		this.fireEvent('onDrawArea', this.currentid);
1923 		xdiff = x - this.memory[this.currentid].downx;
1924 		ydiff = y - this.memory[this.currentid].downy;
1925 		if (Math.abs(xdiff) < Math.abs(ydiff)) {
1926 			diff = Math.abs(parseInt(xdiff, 10));
1927 		}
1928 		else {
1929 			diff = Math.abs(parseInt(ydiff, 10));
1930 		}
1931 		//alert(xdiff);
1932 		this.setAreaSize(this.currentid, diff, diff);
1933 		if (xdiff < 0) {
1934 			this.areas[this.currentid].style.left = (this.memory[this.currentid].downx + diff*-1) + 'px';
1935 		}
1936 		if (ydiff < 0) {
1937 			this.areas[this.currentid].style.top = (this.memory[this.currentid].downy + diff*-1 + 1) + 'px';
1938 		}
1939 	}
1940 	else if (this.is_drawing == this.DM_POLYGON_DRAW || this.is_drawing == this.DM_BEZIER_DRAW) {
1941 		//polygon or bezier mode
1942 		this.fireEvent('onDrawArea', this.currentid);
1943 		this._polygongrow(this.areas[this.currentid], x, y);
1944 	}
1945 	else if (this.is_drawing == this.DM_RECTANGLE_MOVE || this.is_drawing == this.DM_SQUARE_MOVE) {
1946 		this.fireEvent('onMoveArea', this.currentid);
1947 		x = x - this.memory[this.currentid].rdownx;
1948 		y = y - this.memory[this.currentid].rdowny;
1949 		if (x + width > this.pic.width || y + height > this.pic.height) {return;}
1950 		if (x < 0 || y < 0) {return;}
1951 		//this.log(x + ' - '+width+ '+'+this.memory[this.currentid].rdownx +'='+xdiff );
1952 		this.areas[this.currentid].style.left = x + 1 + 'px';
1953 		this.areas[this.currentid].style.top  = y + 1 + 'px';
1954 	}
1955 	else if (this.is_drawing == this.DM_POLYGON_MOVE || this.is_drawing == this.DM_BEZIER_MOVE) {
1956 		this.fireEvent('onMoveArea', this.currentid);
1957 		x = x - this.memory[this.currentid].rdownx;
1958 		y = y - this.memory[this.currentid].rdowny;
1959 		if (x + width > this.pic.width || y + height > this.pic.height) {return;}
1960 		if (x < 0 || y < 0) {return;}
1961 		xdiff = x - left;
1962 		ydiff = y - top;
1963 		if (this.areas[this.currentid].xpoints) {
1964 			for (var i=0, le = this.areas[this.currentid].xpoints.length; i<le; i++) {
1965 				this.areas[this.currentid].xpoints[i] = this.memory[this.currentid].xpoints[i] + xdiff;
1966 				this.areas[this.currentid].ypoints[i] = this.memory[this.currentid].ypoints[i] + ydiff;
1967 			}
1968 		}
1969 		this.areas[this.currentid].style.left = x + 'px';
1970 		this.areas[this.currentid].style.top  = y + 'px';
1971 	}
1972 	else if (this.is_drawing == this.DM_SQUARE_RESIZE_LEFT) {
1973 		this.fireEvent('onResizeArea', this.currentid);
1974 		diff = x - left;
1975 		//alert(diff);
1976 		if ((width  + (-1 * diff)) > 0) {
1977 			//real resize left
1978 			this.areas[this.currentid].style.left   = x + 1 + 'px';
1979 			this.areas[this.currentid].style.top    = (top    + (diff/2)) + 'px';
1980 			this.setAreaSize(this.currentid, parseInt(width  + (-1 * diff), 10), parseInt(height + (-1 * diff), 10));
1981 		}
1982 		else {
1983 			//jump to another state
1984 			this.memory[this.currentid].width  = 0;
1985 			this.memory[this.currentid].height = 0;
1986 			this.memory[this.currentid].left   = x;
1987 			this.memory[this.currentid].top    = y;
1988 			this.is_drawing = this.DM_SQUARE_RESIZE_RIGHT;
1989 		}
1990 	}
1991 	else if (this.is_drawing == this.DM_SQUARE_RESIZE_RIGHT) {
1992 		this.fireEvent('onResizeArea', this.currentid);
1993 		diff = x - left - width;
1994 		if ((width  + (diff)) - 1 > 0) {
1995 			//real resize right
1996 			this.areas[this.currentid].style.top    = (top    + (-1* diff/2)) + 'px';
1997 			this.setAreaSize(this.currentid, (width  + (diff)) - 1, (height + (diff)));
1998 		}
1999 		else {
2000 			//jump to another state
2001 			this.memory[this.currentid].width  = 0;
2002 			this.memory[this.currentid].height = 0;
2003 			this.memory[this.currentid].left   = x;
2004 			this.memory[this.currentid].top    = y;
2005 			this.is_drawing = this.DM_SQUARE_RESIZE_LEFT;
2006 		}
2007 	}
2008 	else if (this.is_drawing == this.DM_SQUARE_RESIZE_TOP) {
2009 		this.fireEvent('onResizeArea', this.currentid);
2010 		diff = y - top;
2011 		if ((width  + (-1 * diff)) > 0) {
2012 			//real resize top
2013 			this.areas[this.currentid].style.top    = y + 1 + 'px';
2014 			this.areas[this.currentid].style.left   = (left   + (diff/2)) + 'px';
2015 			this.setAreaSize(this.currentid, (width  + (-1 * diff)), (height + (-1 * diff)));
2016 		}
2017 		else {
2018 			//jump to another state
2019 			this.memory[this.currentid].width  = 0;
2020 			this.memory[this.currentid].height = 0;
2021 			this.memory[this.currentid].left   = x;
2022 			this.memory[this.currentid].top    = y;
2023 			this.is_drawing = this.DM_SQUARE_RESIZE_BOTTOM;
2024 		}
2025 	}
2026 	else if (this.is_drawing == this.DM_SQUARE_RESIZE_BOTTOM) {
2027 		this.fireEvent('onResizeArea', this.currentid);
2028 		diff = y - top - height;
2029 		if ((width  + (diff)) - 1 > 0) {
2030 			//real resize bottom
2031 			this.areas[this.currentid].style.left   = (left   + (-1* diff/2)) + 'px';
2032 			this.setAreaSize(this.currentid, (width  + (diff)) - 1 , (height + (diff)) - 1);
2033 		}
2034 		else {
2035 			//jump to another state
2036 			this.memory[this.currentid].width  = 0;
2037 			this.memory[this.currentid].height = 0;
2038 			this.memory[this.currentid].left   = x;
2039 			this.memory[this.currentid].top    = y;
2040 			this.is_drawing = this.DM_SQUARE_RESIZE_TOP;
2041 		}
2042 	}
2043 	else if (this.is_drawing == this.DM_RECTANGLE_RESIZE_LEFT) {
2044 		this.fireEvent('onResizeArea', this.currentid);
2045 		xdiff = x - left;
2046 		if (width + (-1 * xdiff) > 0) {
2047 			//real resize left
2048 			this.areas[this.currentid].style.left = x + 1 + 'px';
2049 			this.setAreaSize(this.currentid, width + (-1 * xdiff), null);
2050 		}
2051 		else {
2052 			//jump to another state
2053 			this.memory[this.currentid].width = 0;
2054 			this.memory[this.currentid].left  = x;
2055 			this.is_drawing = this.DM_RECTANGLE_RESIZE_RIGHT;
2056 		}
2057 	}
2058 	else if (this.is_drawing == this.DM_RECTANGLE_RESIZE_RIGHT) {
2059 		this.fireEvent('onResizeArea', this.currentid);
2060 		xdiff = x - left - width;
2061 		if ((width  + (xdiff)) - 1 > 0) {
2062 			//real resize right
2063 			this.setAreaSize(this.currentid, (width  + (xdiff)) - 1, null);
2064 		}
2065 		else {
2066 			//jump to another state
2067 			this.memory[this.currentid].width = 0;
2068 			this.memory[this.currentid].left  = x;
2069 			this.is_drawing = this.DM_RECTANGLE_RESIZE_LEFT;
2070 		}
2071 	}
2072 	else if (this.is_drawing == this.DM_RECTANGLE_RESIZE_TOP) {
2073 		this.fireEvent('onResizeArea', this.currentid);
2074 		ydiff = y - top;
2075 		if ((height + (-1 * ydiff)) > 0) {
2076 			//real resize top
2077 			this.areas[this.currentid].style.top   = y + 1 + 'px';
2078 			this.setAreaSize(this.currentid, null, (height + (-1 * ydiff)));
2079 		}
2080 		else {
2081 			//jump to another state
2082 			this.memory[this.currentid].height = 0;
2083 			this.memory[this.currentid].top    = y;
2084 			this.is_drawing = this.DM_RECTANGLE_RESIZE_BOTTOM;
2085 		}
2086 	}
2087 	else if (this.is_drawing == this.DM_RECTANGLE_RESIZE_BOTTOM) {
2088 		this.fireEvent('onResizeArea', this.currentid);
2089 		ydiff = y - top - height;
2090 		if ((height + (ydiff)) - 1 > 0) {
2091 			//real resize bottom
2092 			this.setAreaSize(this.currentid, null, (height + (ydiff)) - 1);
2093 		}
2094 		else {
2095 			//jump to another state
2096 			this.memory[this.currentid].height = 0;
2097 			this.memory[this.currentid].top    = y;
2098 			this.is_drawing = this.DM_RECTANGLE_RESIZE_TOP;
2099 		}
2100 	}
2101 	
2102 	//repaint canvas elements
2103 	if (this.is_drawing) {
2104 		this._repaint(this.areas[this.currentid], this.config.CL_DRAW_SHAPE, x, y);
2105 		this._updatecoords(this.currentid);
2106 	}
2107 
2108 };
2109 
2110 
2111 /**
2112  *	EVENT HANDLER: Handles mouseup on the image.
2113  *	Handles dragging and resizing.
2114  *	@param	e	The event object.
2115  */
2116 imgmap.prototype.img_mouseup = function(e) {
2117 	if (this.viewmode === 1) {return;}//exit if preview mode
2118 	//console.log('img_mouseup');
2119 	//if (!this.props[this.currentid]) return;
2120 	var pos = this._getPos(this.pic);
2121 	var x = (this.isMSIE) ? (window.event.x - this.pic.offsetLeft) : (e.pageX - pos.x);
2122 	var y = (this.isMSIE) ? (window.event.y - this.pic.offsetTop)  : (e.pageY - pos.y);
2123 	x = x + this.pic_container.scrollLeft;
2124 	y = y + this.pic_container.scrollTop;
2125 	//for everything that is move or resize
2126 	if (this.is_drawing != this.DM_RECTANGLE_DRAW &&
2127 		this.is_drawing != this.DM_SQUARE_DRAW &&
2128 		this.is_drawing != this.DM_POLYGON_DRAW &&
2129 		this.is_drawing != this.DM_POLYGON_LASTDRAW &&
2130 		this.is_drawing != this.DM_BEZIER_DRAW &&
2131 		this.is_drawing != this.DM_BEZIER_LASTDRAW) {
2132 		//end dragging
2133 		this.draggedId = null;
2134 		//finish state
2135 		this.is_drawing = 0;
2136 		this.statusMessage(this.strings.READY);
2137 		this.relaxArea(this.currentid);
2138 		if (this.areas[this.currentid] == this._getLastArea()) {
2139 			//if (this.config.mode != "editor2") this.addNewArea();
2140 			return;
2141 		}
2142 		this.memory[this.currentid].downx  = x;
2143 		this.memory[this.currentid].downy  = y;
2144 	}
2145 };
2146 
2147 
2148 /**
2149  *	EVENT HANDLER: Handles mousedown on the image.
2150  *	Handles beggining or end of draw, or polygon/bezier point set.
2151  *	@param	e	The event object.
2152  */
2153 imgmap.prototype.img_mousedown = function(e) {
2154 	if (this.viewmode === 1) {return;}//exit if preview mode
2155 	if (!this.areas[this.currentid] && this.config.mode != "editor2") {return;}
2156 	//console.log('img_mousedown');
2157 	var pos = this._getPos(this.pic);
2158 
2159 	var x = (this.isMSIE) ? (window.event.x - this.pic.offsetLeft) : (e.pageX - pos.x);
2160 	var y = (this.isMSIE) ? (window.event.y - this.pic.offsetTop)  : (e.pageY - pos.y);
2161 	x = x + this.pic_container.scrollLeft;
2162 	y = y + this.pic_container.scrollTop;
2163 	
2164 	// Handle the Shift state
2165 	if (!e) {
2166 		e = window.event;
2167 	}
2168 
2169 	if (e.shiftKey)	{
2170 		if (this.is_drawing == this.DM_POLYGON_DRAW) {
2171 			this.is_drawing = this.DM_POLYGON_LASTDRAW;
2172 		}
2173 		else if (this.is_drawing == this.DM_BEZIER_DRAW) {
2174 			this.is_drawing = this.DM_BEZIER_LASTDRAW;
2175 		}
2176 	}
2177 	//console.log(this.is_drawing);
2178 	//this.statusMessage(x + ' - ' + y + ': ' + this.props[this.currentid].getElementsByTagName('select')[0].value);
2179 	if (this.is_drawing == this.DM_POLYGON_DRAW || this.is_drawing == this.DM_BEZIER_DRAW) {
2180 		//its not finish state yet
2181 		this.areas[this.currentid].xpoints[this.areas[this.currentid].xpoints.length] = x - 5;
2182 		this.areas[this.currentid].ypoints[this.areas[this.currentid].ypoints.length] = y - 5;
2183 		this.memory[this.currentid].downx  = x;
2184 		this.memory[this.currentid].downy  = y;
2185 		return;
2186 	}
2187 	else if (this.is_drawing && this.is_drawing != this.DM_POLYGON_DRAW && this.is_drawing != this.DM_BEZIER_DRAW) {
2188 		//finish any other state
2189 		if (this.is_drawing == this.DM_POLYGON_LASTDRAW || this.is_drawing == this.DM_BEZIER_LASTDRAW) {
2190 			//add last controlpoint and update coords
2191 			this.areas[this.currentid].xpoints[this.areas[this.currentid].xpoints.length] = x - 5;
2192 			this.areas[this.currentid].ypoints[this.areas[this.currentid].ypoints.length] = y - 5;
2193 			this._updatecoords(this.currentid);
2194 			this.is_drawing = 0;
2195 			this._polygonshrink(this.areas[this.currentid]);
2196 		}
2197 		this.is_drawing = 0;
2198 		this.statusMessage(this.strings.READY);
2199 		this.relaxArea(this.currentid);
2200 		if (this.areas[this.currentid] == this._getLastArea()) {
2201 			//editor mode adds next area automatically
2202 			if (this.config.mode != "editor2") {this.addNewArea();}
2203 			return;
2204 		}
2205 		return;
2206 	}
2207 	
2208 	if (this.config.mode == "editor2") {
2209 		if (!this.nextShape) {return;}
2210 		this.addNewArea();
2211 		//console.log("init: " + this.nextShape);
2212 		this.initArea(this.currentid, this.nextShape);
2213 	}
2214 	else if (this.areas[this.currentid].shape == 'undefined' || this.areas[this.currentid].shape == 'poly') {
2215 		//var shape = (this.props[this.currentid]) ? this.props[this.currentid].getElementsByTagName('select')[0].value : this.nextShape;
2216 		var shape = this.nextShape;
2217 		if (!shape) {shape = 'rect';}
2218 		//console.log("init: " + shape);
2219 		this.initArea(this.currentid, shape);
2220 	}
2221 	if (this.areas[this.currentid].shape == 'poly') {
2222 		this.is_drawing = this.DM_POLYGON_DRAW;
2223 		this.statusMessage(this.strings.POLYGON_DRAW);
2224 		
2225 		this.areas[this.currentid].style.left = x + 'px';
2226 		this.areas[this.currentid].style.top  = y + 'px';
2227 		this.areas[this.currentid].style.width  = 0;
2228 		this.areas[this.currentid].style.height = 0;
2229 		this.areas[this.currentid].xpoints = [];
2230 		this.areas[this.currentid].ypoints = [];
2231 		this.areas[this.currentid].xpoints[0] = x;
2232 		this.areas[this.currentid].ypoints[0] = y;
2233 	}
2234 	else if (this.areas[this.currentid].shape == 'bezier1') {
2235 		this.is_drawing = this.DM_BEZIER_DRAW;
2236 		this.statusMessage(this.strings.BEZIER_DRAW);
2237 		
2238 		this.areas[this.currentid].style.left = x + 'px';
2239 		this.areas[this.currentid].style.top  = y + 'px';
2240 		this.areas[this.currentid].style.width  = 0;
2241 		this.areas[this.currentid].style.height = 0;
2242 		this.areas[this.currentid].xpoints = [];
2243 		this.areas[this.currentid].ypoints = [];
2244 		this.areas[this.currentid].xpoints[0] = x;
2245 		this.areas[this.currentid].ypoints[0] = y;
2246 	}
2247 	else if (this.areas[this.currentid].shape == 'rect') {
2248 		this.is_drawing = this.DM_RECTANGLE_DRAW;
2249 		this.statusMessage(this.strings.RECTANGLE_DRAW);
2250 		
2251 		this.areas[this.currentid].style.left = x + 'px';
2252 		this.areas[this.currentid].style.top  = y + 'px';
2253 		this.areas[this.currentid].style.width  = 0;
2254 		this.areas[this.currentid].style.height = 0;
2255 	}
2256 	else if (this.areas[this.currentid].shape == 'circle') {
2257 		this.is_drawing = this.DM_SQUARE_DRAW;
2258 		this.statusMessage(this.strings.SQUARE_DRAW);
2259 				
2260 		this.areas[this.currentid].style.left = x + 'px';
2261 		this.areas[this.currentid].style.top  = y + 'px';
2262 		this.areas[this.currentid].style.width  = 0;
2263 		this.areas[this.currentid].style.height = 0;
2264 	}
2265 
2266 	this._setBorder(this.currentid, 'DRAW');
2267 	this.memory[this.currentid].downx  = x;
2268 	this.memory[this.currentid].downy  = y;
2269 };
2270 
2271 
2272 /**
2273  *	Highlights a given area.
2274  *	Sets opacity and repaints. 
2275  *	@date	2007.12.28. 18:23:00
2276  *	@param	id	The id of the area to blur.
2277  *	@param	flag	Modifier, possible values: grad - for gradual fade in
2278  */
2279 imgmap.prototype.highlightArea = function(id, flag) {
2280 	if (this.is_drawing) {return;}//exit if in drawing state
2281 	if (this.areas[id] && this.areas[id].shape != 'undefined') {
2282 		//area exists - highlight it
2283 		this.fireEvent('onFocusArea', this.areas[id]);
2284 		this._setBorder(id, 'HIGHLIGHT');
2285 		var opacity = this.config.highlight_opacity;
2286 		if (flag == 'grad') {
2287 			//apply gradient opacity
2288 			opacity = '-' + opacity;
2289 		}
2290 		this._setopacity(this.areas[id], this.config.CL_HIGHLIGHT_BG, opacity);
2291 		this._repaint(this.areas[id], this.config.CL_HIGHLIGHT_SHAPE);
2292 	}
2293 };
2294 
2295 
2296 /**
2297  *	Blurs a given area.
2298  *	Sets opacity and repaints. 
2299  *	@date	2007.12.28. 18:23:26
2300  *	@param	id	The id of the area to blur.
2301  *	@param	flag	Modifier, possible values: grad - for gradual fade out
2302  */
2303 imgmap.prototype.blurArea = function(id, flag) {
2304 	if (this.is_drawing) {return;}//exit if in drawing state
2305 	if (this.areas[id] && this.areas[id].shape != 'undefined') {
2306 		//area exists - fade it back
2307 		this.fireEvent('onBlurArea', this.areas[id]);
2308 		this._setBorder(id, 'NORM');
2309 		var opacity = this.config.norm_opacity;
2310 		if (flag == 'grad') {
2311 			//apply gradient opacity
2312 			opacity = '-' + opacity;
2313 		}
2314 		this._setopacity(this.areas[id], this.config.CL_NORM_BG, opacity);
2315 		this._repaint(this.areas[id], this.config.CL_NORM_SHAPE);
2316 	}
2317 };
2318 
2319 
2320 /**
2321  *	EVENT HANDLER: Handles event of mousemove on imgmap areas.
2322  *	- changes cursor depending where we are inside the area (buggy in opera)
2323  *	- handles area resize
2324  *	- handles area move   
2325  *	@url	http://evolt.org/article/Mission_Impossible_mouse_position/17/23335/index.html
2326  *	@url	http://my.opera.com/community/forums/topic.dml?id=239498&t=1217158015&page=1 
2327  *	@author	adam 
2328  *	@param	e	The event object.
2329  */
2330 imgmap.prototype.area_mousemove = function(e) {
2331 	if (this.viewmode === 1) {return;}//exit if preview mode
2332 	if (!this.is_drawing) {
2333 		var obj = (this.isMSIE) ? window.event.srcElement : e.currentTarget;
2334 		if (obj.tagName == 'DIV') {
2335 			//do this because of label
2336 			obj = obj.parentNode;
2337 		}
2338 		if (obj.tagName == 'image' || obj.tagName == 'group' ||
2339 			obj.tagName == 'shape' || obj.tagName == 'stroke') {
2340 			//do this because of excanvas
2341 			obj = obj.parentNode.parentNode;
2342 		}
2343 		//opera fix - adam - 04-12-2007 23:14:05
2344 		if (this.isOpera) {
2345 			e.layerX = e.offsetX;
2346 			e.layerY = e.offsetY;
2347 		}
2348 		var xdiff = (this.isMSIE) ? (window.event.offsetX) : (e.layerX);
2349 		var ydiff = (this.isMSIE) ? (window.event.offsetY) : (e.layerY);
2350 		//this.log(obj.aid + ' : ' + xdiff + ',' + ydiff);
2351 		var resizable = (obj.shape == 'rect' || obj.shape == 'circle');
2352 		if (resizable && xdiff < 6 && ydiff > 6) {
2353 			//move left
2354 			obj.style.cursor = 'w-resize';
2355 		}
2356 		else if (resizable && xdiff > parseInt(obj.style.width, 10) - 6  && ydiff > 6) {
2357 			//move right
2358 			obj.style.cursor = 'e-resize';
2359 		}
2360 		else if (resizable && xdiff > 6 && ydiff < 6) {
2361 			//move top
2362 			obj.style.cursor = 'n-resize';
2363 		}
2364 		else if (resizable && ydiff > parseInt(obj.style.height, 10) - 6  && xdiff > 6) {
2365 			//move bottom
2366 			obj.style.cursor = 's-resize';
2367 		}
2368 		else {
2369 			//move all
2370 			obj.style.cursor = 'move';
2371 		}
2372 		if (obj.aid != this.draggedId) {
2373 			//not dragged or different
2374 			if (obj.style.cursor == 'move') {obj.style.cursor = 'default';}
2375 			return;
2376 		}
2377 		//moved here from mousedown
2378 		if (xdiff < 6 && ydiff > 6) {
2379 			//move left
2380 			if (this.areas[this.currentid].shape == 'circle') {
2381 				this.is_drawing = this.DM_SQUARE_RESIZE_LEFT;
2382 				this.statusMessage(this.strings.SQUARE_RESIZE_LEFT);
2383 			}
2384 			else if (this.areas[this.currentid].shape == 'rect') {
2385 				this.is_drawing = this.DM_RECTANGLE_RESIZE_LEFT;
2386 				this.statusMessage(this.strings.RECTANGLE_RESIZE_LEFT);
2387 			}
2388 		}
2389 		else if (xdiff > parseInt(this.areas[this.currentid].style.width, 10) - 6  && ydiff > 6) {
2390 			//move right
2391 			if (this.areas[this.currentid].shape == 'circle') {
2392 				this.is_drawing = this.DM_SQUARE_RESIZE_RIGHT;
2393 				this.statusMessage(this.strings.SQUARE_RESIZE_RIGHT);
2394 			}
2395 			else if (this.areas[this.currentid].shape == 'rect') {
2396 				this.is_drawing = this.DM_RECTANGLE_RESIZE_RIGHT;
2397 				this.statusMessage(this.strings.RECTANGLE_RESIZE_RIGHT);
2398 			}
2399 		}
2400 		else if (xdiff > 6 && ydiff < 6) {
2401 			//move top
2402 			if (this.areas[this.currentid].shape == 'circle') {
2403 				this.is_drawing = this.DM_SQUARE_RESIZE_TOP;
2404 				this.statusMessage(this.strings.SQUARE_RESIZE_TOP);
2405 			}
2406 			else if (this.areas[this.currentid].shape == 'rect') {
2407 				this.is_drawing = this.DM_RECTANGLE_RESIZE_TOP;
2408 				this.statusMessage(this.strings.RECTANGLE_RESIZE_TOP);
2409 			}
2410 		}
2411 		else if (ydiff > parseInt(this.areas[this.currentid].style.height, 10) - 6  && xdiff > 6) {
2412 			//move bottom
2413 			if (this.areas[this.currentid].shape == 'circle') {
2414 				this.is_drawing = this.DM_SQUARE_RESIZE_BOTTOM;
2415 				this.statusMessage(this.strings.SQUARE_RESIZE_BOTTOM);
2416 			}
2417 			else if (this.areas[this.currentid].shape == 'rect') {
2418 				this.is_drawing = this.DM_RECTANGLE_RESIZE_BOTTOM;
2419 				this.statusMessage(this.strings.RECTANGLE_RESIZE_BOTTOM);
2420 			}
2421 		}
2422 		else/*if (xdiff < 10 && ydiff < 10 ) */{
2423 			//move all
2424 			if (this.areas[this.currentid].shape == 'circle') {
2425 				this.is_drawing = this.DM_SQUARE_MOVE;
2426 				this.statusMessage(this.strings.SQUARE_MOVE);
2427 				this.memory[this.currentid].rdownx = xdiff;
2428 				this.memory[this.currentid].rdowny = ydiff;
2429 			}
2430 			else if (this.areas[this.currentid].shape == 'rect') {
2431 				this.is_drawing = this.DM_RECTANGLE_MOVE;
2432 				this.statusMessage(this.strings.RECTANGLE_MOVE);
2433 				this.memory[this.currentid].rdownx = xdiff;
2434 				this.memory[this.currentid].rdowny = ydiff;
2435 			}
2436 			else if (this.areas[this.currentid].shape == 'poly' || this.areas[this.currentid].shape == 'bezier1') {
2437 				if (this.areas[this.currentid].xpoints) {
2438 					for (var i=0, le = this.areas[this.currentid].xpoints.length; i<le; i++) {
2439 						this.memory[this.currentid].xpoints[i] = this.areas[this.currentid].xpoints[i];
2440 						this.memory[this.currentid].ypoints[i] = this.areas[this.currentid].ypoints[i];
2441 					}
2442 				}
2443 				
2444 				if (this.areas[this.currentid].shape == 'poly') {
2445 					this.is_drawing = this.DM_POLYGON_MOVE;
2446 					this.statusMessage(this.strings.POLYGON_MOVE);
2447 				}
2448 				else if (this.areas[this.currentid].shape == 'bezier1') {
2449 					this.is_drawing = this.DM_BEZIER_MOVE;
2450 					this.statusMessage(this.strings.BEZIER_MOVE);
2451 				}
2452 				
2453 				this.memory[this.currentid].rdownx = xdiff;
2454 				this.memory[this.currentid].rdowny = ydiff;
2455 			}
2456 		}
2457 		
2458 		//common memory settings (preparing to move or resize)
2459 		this.memory[this.currentid].width  = parseInt(this.areas[this.currentid].style.width, 10);
2460 		this.memory[this.currentid].height = parseInt(this.areas[this.currentid].style.height, 10);
2461 		this.memory[this.currentid].top    = parseInt(this.areas[this.currentid].style.top, 10);
2462 		this.memory[this.currentid].left   = parseInt(this.areas[this.currentid].style.left, 10);
2463 		this._setBorder(this.currentid, 'DRAW');
2464 		this._setopacity(this.areas[this.currentid], this.config.CL_DRAW_BG, this.config.draw_opacity);
2465 	}
2466 	else {
2467 		//if drawing and not ie, have to propagate to image event
2468 		this.img_mousemove(e);
2469 	}
2470 };
2471 
2472 
2473 /**
2474  *	EVENT HANDLER: Handles event of mouseup on imgmap areas.
2475  *	Basically clears draggedId.
2476  *	@author	adam
2477  *	@param	e	The event object
2478  */
2479 imgmap.prototype.area_mouseup = function(e) {
2480 	if (this.viewmode === 1) {return;}//exit if preview mode
2481 	//console.log('area_mouseup');
2482 	if (!this.is_drawing) {
2483 		var obj = (this.isMSIE) ? window.event.srcElement : e.currentTarget;
2484 		if (obj.tagName == 'DIV') {
2485 			//do this because of label
2486 			obj = obj.parentNode;
2487 		}
2488 		if (obj.tagName == 'image' || obj.tagName == 'group' ||
2489 			obj.tagName == 'shape' || obj.tagName == 'stroke') {
2490 			//do this because of excanvas
2491 			obj = obj.parentNode.parentNode;
2492 		}
2493 		if (this.areas[this.currentid] != obj) {
2494 			//trying to draw on a different canvas,switch to this one
2495 			if (typeof obj.aid == 'undefined') {
2496 				this.log('Cannot identify target area', 1);
2497 				return;
2498 			}
2499 			//this.form_selectRow(obj.aid, true);
2500 			//this.currentid = obj.aid;
2501 		}
2502 		this.draggedId = null;
2503 	}
2504 	else {
2505 		//if drawing and not ie, have to propagate to image event
2506 		//console.log('propup');
2507 		this.img_mouseup(e);
2508 	}
2509 };
2510 
2511 
2512 /**
2513  *	EVENT HANDLER: Handles event of mouseover on imgmap areas.
2514  *	Calls gradual highlight on the given area.
2515  *	@author	adam
2516  *	@param	e	The event object
2517  */
2518 imgmap.prototype.area_mouseover = function(e) {
2519 	if (this.viewmode === 1 && this.config.mode !== 'highlighter_spawn') {return;}//exit if preview mode
2520 	if (!this.is_drawing) {
2521 		//this.log('area_mouseover');
2522 		//identify source object
2523 		var obj = (this.isMSIE) ? window.event.srcElement : e.currentTarget;
2524 		if (obj.tagName == 'DIV') {
2525 			//do this because of label
2526 			obj = obj.parentNode;
2527 		}
2528 		if (obj.tagName == 'image' || obj.tagName == 'group' ||
2529 			obj.tagName == 'shape' || obj.tagName == 'stroke') {
2530 			//do this because of excanvas
2531 			obj = obj.parentNode.parentNode;
2532 		}
2533 		/*
2534 		//switch to hovered area
2535 		if (this.areas[this.currentid] != obj) {
2536 			//trying to draw on a different canvas, switch to this one
2537 			if (typeof obj.aid == 'undefined') {
2538 				this.log('Cannot identify target area', 1);
2539 				return;
2540 			}
2541 			this.form_selectRow(obj.aid, true);
2542 			this.currentid = obj.aid;
2543 		}
2544 		*/
2545 		this.highlightArea(obj.aid, 'grad');
2546 	}
2547 };
2548 
2549 
2550 /**
2551  *	EVENT HANDLER: Handles event of mouseout on imgmap areas.
2552  *	Calls gradient blur on the given area.
2553  *	@author	adam
2554  *	@param	e	The event object 
2555  */
2556 imgmap.prototype.area_mouseout = function(e) {
2557 	if (this.viewmode === 1 && this.config.mode !== 'highlighter_spawn') {return;}//exit if preview mode
2558 	if (!this.is_drawing) {
2559 		//this.log('area_mouseout');
2560 		//identify source object
2561 		var obj = (this.isMSIE) ? window.event.srcElement : e.currentTarget;
2562 		if (obj.tagName == 'DIV') {
2563 			//do this because of label
2564 			obj = obj.parentNode;
2565 		}
2566 		if (obj.tagName == 'image' || obj.tagName == 'group' ||
2567 			obj.tagName == 'shape' || obj.tagName == 'stroke') {
2568 			//do this because of excanvas
2569 			obj = obj.parentNode.parentNode;
2570 		}
2571 		this.blurArea(obj.aid, 'grad');
2572 	}
2573 };
2574 
2575 
2576 /**
2577  *	EVENT HANDLER: Handles event of double click on imgmap areas.
2578  *	Basically only fires the custom callback.
2579  *	@author	Colin Bell
2580  *	@param	e	The event object
2581  */
2582 imgmap.prototype.area_dblclick = function(e) {
2583 	if (this.viewmode === 1) {return;}//exit if preview mode
2584 	//console.log('area_dblclick');
2585 	if (!this.is_drawing) {
2586 		var obj = (this.isMSIE) ? window.event.srcElement : e.currentTarget;
2587 		if (obj.tagName == 'DIV') {
2588 			//do this because of label
2589 			obj = obj.parentNode;
2590 		}
2591 		if (obj.tagName == 'image' || obj.tagName == 'group' ||
2592 			obj.tagName == 'shape' || obj.tagName == 'stroke') {
2593 			//do this because of excanvas
2594 			obj = obj.parentNode.parentNode;
2595 		}
2596 		if (this.areas[this.currentid] != obj) {
2597 			//trying to draw on a different canvas, switch to this one
2598 			if (typeof obj.aid == 'undefined') {
2599 				this.log('Cannot identify target area', 1);
2600 				return;
2601 			}
2602 			this.currentid = obj.aid;
2603 		}
2604 		this.fireEvent('onDblClickArea', this.areas[this.currentid]);
2605 		//stop event propagation to document level
2606 		if (this.isMSIE) {
2607 			window.event.cancelBubble = true;
2608 		}
2609 		else {
2610 			e.stopPropagation();
2611 		}
2612 	}
2613 };
2614 
2615 
2616 /**
2617  *	EVENT HANDLER: Handles event of mousedown on imgmap areas.
2618  *	Sets the variables draggedid, selectedid and currentid to the given area. 
2619  *	@author	adam
2620  *	@param	e	The event object 
2621  */
2622 imgmap.prototype.area_mousedown = function(e) {
2623 	if (this.viewmode === 1 && this.config.mode !== 'highlighter_spawn') {return;}//exit if preview mode
2624 	//console.log('area_mousedown');
2625 	if (!this.is_drawing) {
2626 		var obj = (this.isMSIE) ? window.event.srcElement : e.currentTarget;
2627 		if (obj.tagName == 'DIV') {
2628 			//do this because of label
2629 			obj = obj.parentNode;
2630 		}
2631 		if (obj.tagName == 'image' || obj.tagName == 'group' ||
2632 			obj.tagName == 'shape' || obj.tagName == 'stroke') {
2633 			//do this because of excanvas
2634 			obj = obj.parentNode.parentNode;
2635 		}
2636 		if (this.areas[this.currentid] != obj) {
2637 			//trying to draw on a different canvas, switch to this one
2638 			if (typeof obj.aid == 'undefined') {
2639 				this.log('Cannot identify target area', 1);
2640 				return;
2641 			}
2642 			this.currentid = obj.aid;
2643 		}
2644 		//this.log('selected = '+this.currentid);
2645 		this.draggedId  = this.currentid;
2646 		this.selectedId = this.currentid;
2647 		this.fireEvent('onSelectArea', this.areas[this.currentid]);
2648 		//stop event propagation to document level
2649 		if (this.isMSIE) {
2650 			window.event.cancelBubble = true;
2651 		}
2652 		else {
2653 			e.stopPropagation();
2654 		}
2655 	}
2656 	else {
2657 		//if drawing and not ie, have to propagate to image event
2658 		//console.log('propdown');
2659 		this.img_mousedown(e);
2660 	}
2661 };
2662 
2663 
2664 /**
2665  *	EVENT HANDLER: Handles event 'keydown' on document.
2666  *	Handles SHIFT hold while drawing.
2667  *	Note: Safari doesn't generate keyboard events for modifiers:
2668  *	@url	http://bugs.webkit.org/show_bug.cgi?id=11696 
2669  *	@author	adam
2670  *	@param	e	The event object 
2671  */
2672 imgmap.prototype.doc_keydown = function(e) {
2673 	if (this.viewmode === 1) {return;}//exit if preview mode
2674 	var key = (this.isMSIE) ? event.keyCode : e.keyCode;
2675 	//console.log(key);
2676 	if (key == 46) {
2677 		//delete key pressed
2678 		if (this.selectedId !== null && !this.is_drawing) {this.removeArea(this.selectedId);}
2679 	}
2680 	else if (key == 16) {
2681 		//shift key pressed
2682 		if (this.is_drawing == this.DM_RECTANGLE_DRAW) {
2683 			this.is_drawing = this.DM_SQUARE_DRAW;
2684 			this.statusMessage(this.strings.SQUARE2_DRAW);
2685 		}
2686 	}
2687 };
2688 
2689 
2690 /**
2691  *	EVENT HANDLER: Handles event 'keyup' on document.
2692  *	Handles SHIFT release while drawing.
2693  *	@author	adam
2694  *	@param	e	The event object
2695  */
2696 imgmap.prototype.doc_keyup = function(e) {
2697 	var key = (this.isMSIE) ? event.keyCode : e.keyCode;
2698 	//alert(key);
2699 	if (key == 16) {
2700 		//shift key released
2701 		if (this.is_drawing == this.DM_SQUARE_DRAW && this.areas[this.currentid].shape == 'rect') {
2702 			//not for circle!
2703 			this.is_drawing = this.DM_RECTANGLE_DRAW;
2704 			this.statusMessage(this.strings.RECTANGLE_DRAW);
2705 		}
2706 	}
2707 };
2708 
2709 
2710 /**
2711  *	EVENT HANDLER: Handles event 'mousedown' on document.
2712  *	@author	adam
2713  *	@param	e	The event object 
2714  */
2715 imgmap.prototype.doc_mousedown = function(e) {
2716 	if (this.viewmode === 1) {return;}//exit if preview mode
2717 	if (!this.is_drawing) {
2718 		this.selectedId = null;
2719 	}
2720 };
2721 
2722 
2723 /**
2724  *	Get the real position of the element.
2725  *	Deal with browser differences when trying to get the position of an area.
2726  *	@param	element	The element you want the position of.
2727  *	@return	An object with x and y members.
2728  */
2729 imgmap.prototype._getPos = function(element) {
2730 	var xpos = 0;
2731 	var ypos = 0;
2732 	if (element) {
2733 		var elementOffsetParent = element.offsetParent;
2734 		// If the element has an offset parent
2735 		if (elementOffsetParent) {
2736 			// While there is an offset parent
2737 			while ((elementOffsetParent = element.offsetParent)) {
2738 				//offset might give negative in opera when the image is scrolled
2739 				if (element.offsetLeft > 0) {xpos += element.offsetLeft;}
2740 				if (element.offsetTop > 0) {ypos += element.offsetTop;}
2741 				element = elementOffsetParent;
2742 			}
2743 		}
2744 		else {
2745 			xpos = element.offsetLeft;
2746 			ypos = element.offsetTop;
2747 		}
2748 	}
2749 	return {x: xpos, y: ypos};
2750 };
2751 
2752 
2753 /**
2754  *	Gets the last (visible and editable) area.
2755  *	@author	Adam Maschek (adam.maschek(at)gmail.com)
2756  *	@date	2006-06-15 16:34:51
2757  *	@returns	The last area object or null. 
2758  */
2759 imgmap.prototype._getLastArea = function() {
2760 	for (var i = this.areas.length-1; i>=0; i--) {
2761 		if (this.areas[i]) {
2762 			return this.areas[i];
2763 		}
2764 	}
2765 	return null;
2766 };
2767 
2768 
2769 /**
2770  *	Parses cssText to single style declarations.
2771  *	@author	adam
2772  *	@date	25-09-2007 18:19:51
2773  *	@param obj	The DOM object to apply styles on.
2774  *	@param cssText	The css declarations to apply.
2775  */
2776 imgmap.prototype.assignCSS = function(obj, cssText) {
2777 	var parts = cssText.split(';');
2778 	for (var i = 0; i < parts.length; i++) {
2779 		var p = parts[i].split(':');
2780 		//we need to camelcase by - signs
2781 		var pp = this.trim(p[0]).split('-');
2782 		var prop = pp[0];
2783 		for (var j = 1; j < pp.length; j++) {
2784 			//replace first letters to uppercase
2785 			prop+= pp[j].replace(/^\w/, pp[j].substring(0,1).toUpperCase());
2786 		}
2787 		obj.style[this.trim(prop)] = this.trim(p[1]);
2788 	}
2789 };
2790 
2791 
2792 /**
2793  *	To fire callback hooks on custom events, passing them the object of the event.
2794  *	@author	adam
2795  *	@date	13-10-2007 15:24:49
2796  *	@param evt	The type of event
2797  *	@param obj	The object of the event. (can be an id, a string, an object, whatever is most relevant)  
2798  */   
2799 imgmap.prototype.fireEvent = function(evt, obj) {
2800 	//this.log("Firing event: " + evt);
2801 	if (typeof this.config.custom_callbacks[evt] == 'function') {
2802 		return this.config.custom_callbacks[evt](obj);
2803 	}
2804 };
2805 
2806 
2807 /**
2808  *	To set area dimensions.
2809  *	This is needed to achieve the same result in all browsers. 
2810  *	@author	adam
2811  *	@date	10-12-2007 22:29:41
2812  *	@param	id	The id of the area (canvas) to resize.
2813  *	@param	w	The desired width in pixels.
2814  *	@param	h	The desired height in pixels.
2815  */
2816 imgmap.prototype.setAreaSize = function(id, w, h) {
2817 	if (id === null) {id = this.currentid;}
2818 	if (w !== null) {
2819 		this.areas[id].width  = w;
2820 		this.areas[id].style.width  = (w) + 'px';
2821 		this.areas[id].setAttribute('width',  w);
2822 	}
2823 	if (h !== null) {
2824 		this.areas[id].height = h;
2825 		this.areas[id].style.height = (h) + 'px';
2826 		this.areas[id].setAttribute('height', h);
2827 	}
2828 };
2829 
2830 
2831 /**
2832  *	Tries to detect preferred language of user.
2833  *	@date	2007.12.28. 15:43:46
2834  *	@return The two byte language code. (We dont care now for pt-br, etc.)
2835  */
2836 imgmap.prototype.detectLanguage = function() {
2837 	var lang;
2838 	if (navigator.userLanguage) {
2839 		lang = navigator.userLanguage.toLowerCase();
2840 	}
2841 	else if (navigator.language) {
2842 		lang = navigator.language.toLowerCase();
2843 	}
2844 	else {
2845 		return this.config.defaultLang;
2846 	}
2847 	//this.log(lang, 2);
2848 	if (lang.length >= 2) {
2849 		lang = lang.substring(0,2);
2850 		return lang;
2851 	}
2852 	return this.config.defaultLang;
2853 };
2854 
2855 
2856 /**
2857  *	Disable selection on a given object.
2858  *	This is especially useful in Safari, where dragging around areas
2859  *	keeps selecting all sorts of things.
2860  *	@author	Bret Taylor
2861  *	@url	http://ajaxcookbook.org/disable-text-selection/
2862  *	@date	27-07-2008 1:57:45
2863  *	@param	element	The DOM element on which you want to disable selection.
2864  */       
2865 imgmap.prototype.disableSelection = function(element) {
2866 	if (typeof element == 'undefined' || !element) {return false;}
2867 	if (typeof element.onselectstart != "undefined") {
2868 	    element.onselectstart = function() {
2869 	        return false;
2870 	    };
2871     }
2872     if (typeof element.unselectable != "undefined") {
2873     	element.unselectable = "on";
2874     }
2875     if (typeof element.style.MozUserSelect != "undefined") {
2876     	element.style.MozUserSelect = "none";
2877     }
2878 };
2879 
2880 
2881 /**
2882  *	@date	11-02-2007 19:57:05
2883  *	@url	http://www.deepwood.net/writing/method-references.html.utf8
2884  *	@author	Daniel Brockman
2885  *	@addon
2886  */
2887 Function.prototype.bind = function(object) {
2888 	var method = this;
2889 	return function () {
2890 		return method.apply(object, arguments);
2891 	};
2892 };
2893 
2894 
2895 /**
2896  *	Trims a string.
2897  *	Changed not to extend String but use own function for better compatibility. 
2898  *	@param str The string to trim. 
2899  */
2900 imgmap.prototype.trim = function(str) {
2901 	return str.replace(/^\s+|\s+$/g, '');
2902 };
2903 
2904 
2905 /**
2906  *	Spawn an imgmap object for each imagemap found in the document.
2907  *	This is used for highlighter mode only.
2908  *	@param config	An imgmap config object  
2909  */
2910 function imgmap_spawnObjects(config) {
2911 	//console.log('spawnobjects');
2912 	var maps = document.getElementsByTagName('map');
2913 	var imgs = document.getElementsByTagName('img');
2914 	var imaps = [];
2915 	var imapn;
2916 	//console.log(maps.length);
2917 	for (var i=0, le=maps.length; i<le; i++) {
2918 		for (var j=0, le2=imgs.length; j<le2; j++) {
2919 		//console.log(i);
2920 		//	console.log(maps[i].name);
2921 		//	console.log(imgs[j].getAttribute('usemap'));
2922 			if ('#' + maps[i].name == imgs[j].getAttribute('usemap')) {
2923 				//we found one matching pair
2924 			//	console.log(maps[i]);
2925 				config.mode = 'highlighter_spawn';
2926 				imapn = new imgmap(config);
2927 				//imapn.setup(config);
2928 				imapn.useImage(imgs[j]);
2929 				imapn.setMapHTML(maps[i]);
2930 				imapn.viewmode = 1;
2931 				
2932 				imaps.push(imapn);
2933 				
2934 			}
2935 		}
2936 	}
2937 }
2938 
2939 //global instance?
2940 //imgmap_spawnObjects();?
2941