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