/** * Image Map Editor (imgmap) - in-browser imagemap editor * Copyright (C) 2006 - 2008 Adam Maschek (adam.maschek @ gmail.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @fileoverview * Online Image Map Editor - main script file. * This is the main script file of the Online Image Map Editor. * * TODO: * -scriptload race condition fix * -destroy/cleanup function ? * -testing highlighter * -cursor area_mousemove in opera not refreshing quite well - bug reported * -get rid of memo array * -highlight which control point is edited in html or form mode * -more comments, especially on config vars * -make function names more logical * - dumpconfig * -prepare for bad input /poly not properly closed? * -prepare for % values in coords * -prepare for default shape http://www.w3.org/TR/html4/struct/objects.html#edef-AREA * * @date 26-02-2007 2:24:50 * @author Adam Maschek (adam.maschek(at)gmail.com) * @copyright * @version 2.2 * */ /*jslint browser: true, newcap: false, white: false, onevar: false, plusplus: false, eqeqeq: false, nomen: false */ /*global imgmapStrings:true, window:false, G_vmlCanvasManager:false, air:false, imgmap_spawnObjects:true */ /** * @author Adam Maschek * @constructor * @param config The config object. */ function imgmap(config) { /** Version string of imgmap */ this.version = "2.2"; /** Build date of imgmap */ this.buildDate = "2009/08/12 22:18"; /** Sequential build number of imgmap */ this.buildNumber = "113"; /** Config object of the imgmap instance */ this.config = {}; /** Status flag to indicate current drawing mode */ this.is_drawing = 0; /** Array to hold language strings */ this.strings = []; /** Helper array for some drawing operations */ this.memory = []; /** Array to hold reference to all areas (canvases) */ this.areas = []; /** Array to hold last log entries */ this.logStore = []; /** Associative array to hold bound event handlers */ this.eventHandlers = {}; this.currentid = 0; this.draggedId = null; this.selectedId = null; this.nextShape = 'rect'; /** possible values: 0 - edit, 1 - preview */ this.viewmode = 0; /** array of dynamically loaded javascripts */ this.loadedScripts = []; this.isLoaded = false; this.cntReloads = 0; /** holds the name of the actively edited map, use getMapName to read it */ this.mapname = ''; /** holds the id of the actively edited map, use getMapIdto read it */ this.mapid = ''; /** watermark to attach to output */ this.waterMark = ''; /** global scale of areas (1-normal, 2-doubled, 0.5-half, etc.) */ this.globalscale = 1; /** is_drawing draw mode constant */ this.DM_RECTANGLE_DRAW = 1; /** is_drawing draw mode constant */ this.DM_RECTANGLE_MOVE = 11; /** is_drawing draw mode constant */ this.DM_RECTANGLE_RESIZE_TOP = 12; /** is_drawing draw mode constant */ this.DM_RECTANGLE_RESIZE_RIGHT = 13; /** is_drawing draw mode constant */ this.DM_RECTANGLE_RESIZE_BOTTOM = 14; /** is_drawing draw mode constant */ this.DM_RECTANGLE_RESIZE_LEFT = 15; /** is_drawing draw mode constant */ this.DM_SQUARE_DRAW = 2; /** is_drawing draw mode constant */ this.DM_SQUARE_MOVE = 21; /** is_drawing draw mode constant */ this.DM_SQUARE_RESIZE_TOP = 22; /** is_drawing draw mode constant */ this.DM_SQUARE_RESIZE_RIGHT = 23; /** is_drawing draw mode constant */ this.DM_SQUARE_RESIZE_BOTTOM = 24; /** is_drawing draw mode constant */ this.DM_SQUARE_RESIZE_LEFT = 25; /** is_drawing draw mode constant */ this.DM_POLYGON_DRAW = 3; /** is_drawing draw mode constant */ this.DM_POLYGON_LASTDRAW = 30; /** is_drawing draw mode constant */ this.DM_POLYGON_MOVE = 31; /** is_drawing draw mode constant */ this.DM_BEZIER_DRAW = 4; /** is_drawing draw mode constant */ this.DM_BEZIER_LASTDRAW = 40; /** is_drawing draw mode constant */ this.DM_BEZIER_MOVE = 41; //set some config defaults below /** * Mode of operation * possible values: * editor - classical editor, * editor2 - dreamweaver style editor, * highlighter - map highlighter, will spawn imgmap instances for each map found in the current page * highlighter_spawn - internal mode after spawning imgmap objects */ this.config.mode = "editor"; this.config.baseroot = ''; this.config.lang = ''; this.config.defaultLang = 'en'; this.config.loglevel = 0; this.config.custom_callbacks = {};//possible values: see below! /** Callback events that you can handle in your GUI. */ this.event_types = [ 'onModeChanged', 'onHtmlChanged', 'onAddArea', 'onRemoveArea', 'onDrawArea', 'onResizeArea', 'onRelaxArea', 'onFocusArea', 'onBlurArea', 'onMoveArea', 'onSelectRow', 'onLoadImage', 'onSetMap', 'onGetMap', 'onSelectArea', 'onDblClickArea', 'onStatusMessage', 'onAreaChanged']; //default color values this.config.CL_DRAW_BOX = '#E32636'; this.config.CL_DRAW_SHAPE = '#d00'; this.config.CL_DRAW_BG = '#fff'; this.config.CL_NORM_BOX = '#E32636'; this.config.CL_NORM_SHAPE = '#d00'; this.config.CL_NORM_BG = '#fff'; this.config.CL_HIGHLIGHT_BOX = '#E32636'; this.config.CL_HIGHLIGHT_SHAPE = '#d00'; this.config.CL_HIGHLIGHT_BG = '#fff'; this.config.CL_KNOB = '#555'; this.config.bounding_box = true; this.config.label = '%n'; //the format string of the area labels - possible values: %n - number, %c - coords, %h - href, %a - alt, %t - title this.config.label_class = 'imgmap_label'; //the css class to apply on labels this.config.label_style = 'font: bold 10px Arial'; //this.config.label_style = 'font-weight: bold; font-size: 10px; font-family: Arial; color: #964'; //the css style(s) to apply on labels this.config.hint = '#%n %h'; //the format string of the area mouseover hints - possible values: %n - number, %c - coords, %h - href, %a - alt, %t - title this.config.draw_opacity = '35'; //the opacity value of the area while drawing, moving or resizing - possible values 0 - 100 or range "(x)-y" this.config.norm_opacity = '50'; //the opacity value of the area while relaxed - possible values 0 - 100 or range "(x)-y" this.config.highlight_opacity = '70'; //the opacity value of the area while highlighted - possible values 0 - 100 or range "(x)-y" this.config.cursor_default = 'crosshair'; //auto/pointer //the css cursor while hovering over the image //browser sniff var ua = navigator.userAgent; this.isMSIE = (navigator.appName == "Microsoft Internet Explorer"); this.isMSIE5 = this.isMSIE && (ua.indexOf('MSIE 5') != -1); this.isMSIE5_0 = this.isMSIE && (ua.indexOf('MSIE 5.0') != -1); this.isMSIE7 = this.isMSIE && (ua.indexOf('MSIE 7') != -1); this.isGecko = ua.indexOf('Gecko') != -1; this.isSafari = ua.indexOf('Safari') != -1; this.isOpera = (typeof window.opera != 'undefined'); this.setup(config); } /** * Return an object given by id or object itself. * @date 22-02-2007 0:14:50 * @author Adam Maschek (adam.maschek(at)gmail.com) * @param objorid A DOM object, or id of a DOM object. * @return The identified DOM object or null on error. */ imgmap.prototype.assignOID = function(objorid) { try { if (typeof objorid == 'undefined') { this.log("Undefined object passed to assignOID.");// Called from: " + arguments.callee.caller, 1); return null; } else if (typeof objorid == 'object') { return objorid; } else if (typeof objorid == 'string') { return document.getElementById(objorid); } } catch (err) { this.log("Error in assignOID", 1); } return null; }; /** * Main setup function. * Can be called manually or constructor will call it. * @date 22-02-2007 0:15:42 * @author Adam Maschek (adam.maschek(at)gmail.com) * @param config config object * @return True if all went ok. */ imgmap.prototype.setup = function(config) { //this.log('setup'); //copy non-default config parameters to this.config for (var i in config) { if (config.hasOwnProperty(i)) { this.config[i] = config[i]; } } //set document event hooks this.addEvent(document, 'keydown', this.eventHandlers.doc_keydown = this.doc_keydown.bind(this)); this.addEvent(document, 'keyup', this.eventHandlers.doc_keyup = this.doc_keyup.bind(this)); this.addEvent(document, 'mousedown', this.eventHandlers.doc_mousedown = this.doc_mousedown.bind(this)); //set pic_container element - supposedly it already exists in the DOM if (config && config.pic_container) { this.pic_container = this.assignOID(config.pic_container); this.disableSelection(this.pic_container); } if (!this.config.baseroot) { //search for a base - theoretically there can only be one, but lets search //for the first non-empty var bases = document.getElementsByTagName('base'); var base = ''; for (i=0; i'; //document.write(temp); try { var head = document.getElementsByTagName('head')[0]; var temp = document.createElement('SCRIPT'); temp.setAttribute('language', 'javascript'); temp.setAttribute('type', 'text/javascript'); temp.setAttribute('src', url); //temp.setAttribute('defer', true); head.appendChild(temp); this.addLoadEvent(temp, this.script_load.bind(this)); } catch (err) { this.log('Error loading script: ' + url); } return true; }; /** * EVENT HANDLER: Event handler of external script loaded. * @param e The event object. */ imgmap.prototype.script_load = function(e) { var obj = (this.isMSIE) ? window.event.srcElement : e.currentTarget; var url = obj.src; var complete = false; if (typeof obj.readyState != 'undefined') { //explorer if (obj.readyState == 'complete' || obj.readyState == 'loaded') { complete = true; } } else { //other browsers? complete = true; } if (complete) { this.loadedScripts[url] = 1; this.log('Loaded script: ' + url); return true; } }; /** * Load strings from a key:value object to the prototype strings array. * @author adam * @date 2007 * @param obj Javascript object that holds key:value pairs. */ imgmap.prototype.loadStrings = function(obj) { for (var key in obj) { if (obj.hasOwnProperty(key)) { this.strings[key] = obj[key]; } } }; /** * This function is to load a given img url to the pic_container. * * Loading an image will clear all current maps. * @see #useImage * @param img The imageurl or object to load (if object, function will get url, and do a recall) * @param imgw The width we want to force on the image (optional) * @param imgh The height we want to force on the image (optional) * @returns True on success */ imgmap.prototype.loadImage = function(img, imgw, imgh) { //test for container if (typeof this.pic_container == 'undefined') { this.log('You must have pic_container defined to use loadImage!', 2); return false; } //wipe all this.removeAllAreas(); //reset scale this.globalscale = 1; this.fireEvent('onHtmlChanged', '');//empty if (!this._getLastArea()) { //init with one new area if there was none editable if (this.config.mode != "editor2") {this.addNewArea();} } if (typeof img == 'string') { //there is an image given with url to load if (typeof this.pic == 'undefined') { this.pic = document.createElement('IMG'); this.pic_container.appendChild(this.pic); //event handler hooking - only at the first load this.addEvent(this.pic, 'mousedown', this.eventHandlers.img_mousedown = this.img_mousedown.bind(this)); this.addEvent(this.pic, 'mouseup', this.eventHandlers.img_mouseup = this.img_mouseup.bind(this)); this.addEvent(this.pic, 'mousemove', this.eventHandlers.img_mousemove = this.img_mousemove.bind(this)); this.pic.style.cursor = this.config.cursor_default; } //img ='../../'+img; //this.log('Loading image: ' + img, 0); //calculate timestamp to bypass browser cache mechanism var q = '?'; if (img.indexOf('?') > -1) { q = '&'; } this.pic.src = img + q + (new Date().getTime()); if (imgw && imgw > 0) {this.pic.setAttribute('width', imgw);} if (imgh && imgh > 0) {this.pic.setAttribute('height', imgh);} this.fireEvent('onLoadImage', this.pic); return true; } else if (typeof img == 'object') { //we have to use the src of the image object var src = img.src; //img.getAttribute('src'); if (src === '' && img.getAttribute('mce_src') !== '') { //if it is a tinymce object, it has no src but mce_src attribute! src = img.getAttribute('mce_src'); } else if (src === '' && img.getAttribute('_fcksavedurl') !== '') { //if it is an fck object, it might have only _fcksavedurl attribute! src = img.getAttribute('_fcksavedurl'); } // Get the displayed dimensions of the image if (!imgw) { imgw = img.clientWidth; } if (!imgh) { imgh = img.clientHeight; } //recurse, this time with the url string return this.loadImage(src, imgw, imgh); } }; /** * We use this when there is an existing image object we want to handle with imgmap. * Mainly used in highlighter mode. * @author adam * @see #loadImage * @see #imgmap_spawnObjects * @date 2007 * @param img DOM object or id of image we want to use. */ imgmap.prototype.useImage = function(img) { //wipe all this.removeAllAreas(); if (!this._getLastArea()) { //init with one new area if there was none editable if (this.config.mode != "editor2") {this.addNewArea();} } img = this.assignOID(img); if (typeof img == 'object') { if (typeof this.pic != 'undefined') { //remove previous handlers this.removeEvent(this.pic, 'mousedown', this.eventHandlers.img_mousedown); this.removeEvent(this.pic, 'mouseup', this.eventHandlers.img_mouseup); this.removeEvent(this.pic, 'mousemove', this.eventHandlers.img_mousemove); this.pic.style.cursor = ''; } this.pic = img; //hook event handlers this.addEvent(this.pic, 'mousedown', this.eventHandlers.img_mousedown = this.img_mousedown.bind(this)); this.addEvent(this.pic, 'mouseup', this.eventHandlers.img_mouseup = this.img_mouseup.bind(this)); this.addEvent(this.pic, 'mousemove', this.eventHandlers.img_mousemove = this.img_mousemove.bind(this)); this.pic.style.cursor = this.config.cursor_default; if (this.pic.parentNode.className == 'pic_container') { //use existing container this.pic_container = this.pic.parentNode; } else { //dynamically create container this.pic_container = document.createElement("div"); this.pic_container.className = 'pic_container'; this.pic.parentNode.insertBefore(this.pic_container, this.pic); //ref: If the node already exists it is removed from current parent node, then added to new parent node. this.pic_container.appendChild(this.pic); } this.fireEvent('onLoadImage', this.pic); return true; } }; /** * Fires custom hook onStatusMessage, passing the status string. * Use this to update your GUI. * @author Adam Maschek (adam.maschek(at)gmail.com) * @date 26-07-2008 13:22:54 * @param str The status string */ imgmap.prototype.statusMessage = function(str) { this.fireEvent('onStatusMessage', str); }; /** * Adds basic logging functionality using firebug console object if available. * Also tries to use AIR introspector if available. * @date 20-02-2007 17:55:18 * @author Adam Maschek (adam.maschek(at)gmail.com) * @param obj The object or string you want to debug/echo. * @level level The log level, 0 being the smallest issue. */ imgmap.prototype.log = function(obj, level) { /* if (level === '' || typeof level == 'undefined') {level = 0;} if (this.config.loglevel != -1 && level >= this.config.loglevel) { this.logStore.push({level: level, obj: obj}); } if (typeof console == 'object') { console.log(obj); } else if (this.isOpera) { opera.postError(level + ': ' + obj); } else if (typeof air == 'object') { //we are inside AIR if (typeof air.Introspector == 'object') { air.Introspector.Console.log(obj); } else { air.trace(obj); } } else { if (level > 1) { //alert(level + ': ' + obj); //dump with all pevious errors: var msg = ''; for (var i=0, le = this.logStore.length; i' + this.getMapInnerHTML(flags) + this.waterMark + ''; this.fireEvent('onGetMap', html); //alert(html); return html; }; /** * Get the map areas part only of the current imagemap. * @see #getMapHTML * @author adam * @param flags Currently ony 'noscale' used to prevent scaling of coordinates in preview mode. * @return The generated map code without the map wrapper. */ imgmap.prototype.getMapInnerHTML = function(flags) { var html, coords; html = ''; //foreach area properties for (var i=0, le = this.areas.length; i'; } } } //alert(html); return html; }; /** * Get the map name of the current imagemap. * If doesnt exist, nor map id, generate a new name based on timestamp. * The most portable solution is to use the same value for id and name. * This also conforms the HTML 5 specification, that says: * "If the id attribute is also specified, both attributes must have the same value." * @link http://www.w3.org/html/wg/html5/#the-map-element * @author adam * @see #getMapId * @return The name of the map. */ imgmap.prototype.getMapName = function() { if (this.mapname === '') { if (this.mapid !== '') {return this.mapid;} var now = new Date(); this.mapname = 'imgmap' + now.getFullYear() + (now.getMonth()+1) + now.getDate() + now.getHours() + now.getMinutes() + now.getSeconds(); } return this.mapname; }; /** * Get the map id of the current imagemap. * If doesnt exist, use map name. * @author adam * @see #getMapName * @return The id of the map. */ imgmap.prototype.getMapId = function() { if (this.mapid === '') { this.mapid = this.getMapName(); } return this.mapid; }; /** * Convert wild shape names to normal ones. * @date 25-12-2008 19:27:06 * @param shape The name of the shape to convert. * @return The normalized shape name, rect as default. */ imgmap.prototype._normShape = function(shape) { if (!shape) {return 'rect';} shape = this.trim(shape).toLowerCase(); if (shape.substring(0, 4) == 'rect') {return 'rect';} if (shape.substring(0, 4) == 'circ') {return 'circle';} if (shape.substring(0, 4) == 'poly') {return 'poly';} return 'rect'; }; /** * Try to normalize coordinates that came from: * 1. html textarea * 2. user input in the active area's input field * 3. from the html source in case of plugins or highlighter * Example of inputs that need to be handled: * 035,035 075,062 * 150,217, 190,257, 150,297,110,257 * @author adam * @param coords The coordinates in a string. * @param shape The shape of the object (rect, circle, poly, bezier1). * @param flag Flags that modify the operation. (fromcircle, frompoly, fromrect, preserve) * @returns The normalized coordinates. */ imgmap.prototype._normCoords = function(coords, shape, flag) { //function level var declarations var i;//generic cycle counter var sx;//smallest x var sy;//smallest y var gx;//greatest x var gy;//greatest y var temp, le; //console.log('normcoords: ' + coords + ' - ' + shape + ' - ' + flag); coords = this.trim(coords); if (coords === '') {return '';} var oldcoords = coords; //replace some general junk coords = coords.replace(/(\d)(\D)+(\d)/g, "$1,$3"); coords = coords.replace(/,\D+(\d)/g, ",$1");//cut leading junk coords = coords.replace(/,0+(\d)/g, ",$1");//cut leading zeros coords = coords.replace(/(\d)(\D)+,/g, "$1,"); coords = coords.replace(/^\D+(\d)/g, "$1");//cut leading junk coords = coords.replace(/^0+(\d)/g, "$1");//cut leading zeros coords = coords.replace(/(\d)(\D)+$/g, "$1");//cut trailing junk //console.log('>'+coords + ' - ' + shape + ' - ' + flag); //now fix other issues var parts = coords.split(','); if (shape == 'rect') { if (flag == 'fromcircle') { var r = parts[2]; parts[0] = parts[0] - r; parts[1] = parts[1] - r; parts[2] = parseInt(parts[0], 10) + 2 * r; parts[3] = parseInt(parts[1], 10) + 2 * r; } else if (flag == 'frompoly') { sx = parseInt(parts[0], 10); gx = parseInt(parts[0], 10); sy = parseInt(parts[1], 10); gy = parseInt(parts[1], 10); for (i=0, le = parts.length; i gx) { gx = parseInt(parts[i], 10);} if (i % 2 === 1 && parseInt(parts[i], 10) > gy) { gy = parseInt(parts[i], 10);} //console.log(sx+","+sy+","+gx+","+gy); } parts[0] = sx; parts[1] = sy; parts[2] = gx; parts[3] = gy; } if (!(parseInt(parts[1], 10) >= 0)) {parts[1] = parts[0];} if (!(parseInt(parts[2], 10) >= 0)) {parts[2] = parseInt(parts[0], 10) + 10;} if (!(parseInt(parts[3], 10) >= 0)) {parts[3] = parseInt(parts[1], 10) + 10;} if (parseInt(parts[0], 10) > parseInt(parts[2], 10)) { temp = parts[0]; parts[0] = parts[2]; parts[2] = temp; } if (parseInt(parts[1], 10) > parseInt(parts[3], 10)) { temp = parts[1]; parts[1] = parts[3]; parts[3] = temp; } coords = parts[0]+","+parts[1]+","+parts[2]+","+parts[3]; //console.log(coords); } else if (shape == 'circle') { if (flag == 'fromrect') { sx = parseInt(parts[0], 10); gx = parseInt(parts[2], 10); sy = parseInt(parts[1], 10); gy = parseInt(parts[3], 10); //use smaller side parts[2] = (gx - sx < gy - sy) ? gx - sx : gy - sy; parts[2] = Math.floor(parts[2] / 2);//radius parts[0] = sx + parts[2]; parts[1] = sy + parts[2]; } else if (flag == 'frompoly') { sx = parseInt(parts[0], 10); gx = parseInt(parts[0], 10); sy = parseInt(parts[1], 10); gy = parseInt(parts[1], 10); for (i=0, le = parts.length; i gx) { gx = parseInt(parts[i], 10);} if (i % 2 === 1 && parseInt(parts[i], 10) > gy) { gy = parseInt(parts[i], 10);} //console.log(sx+","+sy+","+gx+","+gy); } //use smaller side parts[2] = (gx - sx < gy - sy) ? gx - sx : gy - sy; parts[2] = Math.floor(parts[2] / 2);//radius parts[0] = sx + parts[2]; parts[1] = sy + parts[2]; } if (!(parseInt(parts[1], 10) > 0)) {parts[1] = parts[0];} if (!(parseInt(parts[2], 10) > 0)) {parts[2] = 10;} coords = parts[0]+","+parts[1]+","+parts[2]; } else if (shape == 'poly') { if (flag == 'fromrect') { parts[4] = parts[2]; parts[5] = parts[3]; parts[2] = parts[0]; parts[6] = parts[4]; parts[7] = parts[1]; } else if (flag == 'fromcircle') { // @ url http://www.pixelwit.com/blog/2007/06/29/basic-circle-drawing-actionscript/ var centerX = parseInt(parts[0], 10); var centerY = parseInt(parts[1], 10); var radius = parseInt(parts[2], 10); var j = 0; parts[j++] = centerX + radius; parts[j++] = centerY; var sides = 60;//constant = sides the fake circle will have for (i=0; i<=sides; i++) { var pointRatio = i/sides; var xSteps = Math.cos(pointRatio*2*Math.PI); var ySteps = Math.sin(pointRatio*2*Math.PI); var pointX = centerX + xSteps * radius; var pointY = centerY + ySteps * radius; parts[j++] = Math.round(pointX); parts[j++] = Math.round(pointY); } //console.log(parts); } coords = parts.join(','); } else if (shape == 'bezier1') { coords = parts.join(','); } if (flag == 'preserve' && oldcoords != coords) { //return original and throw error //throw "invalid coords"; return oldcoords; } return coords; }; /** * Sets the coordinates according to the given HTML map code or DOM object. * @author Adam Maschek (adam.maschek(at)gmail.com) * @date 2006-06-07 11:47:16 * @param map DOM object or string of a map you want to apply. * @return True on success */ imgmap.prototype.setMapHTML = function(map) { if (this.viewmode === 1) {return;}//exit if preview mode this.fireEvent('onSetMap', map); //this.log(map); //remove all areas this.removeAllAreas(); //console.log(this.areas); var oMap; if (typeof map == 'string') { var oHolder = document.createElement('DIV'); oHolder.innerHTML = map; oMap = oHolder.firstChild; } else if (typeof map == 'object') { oMap = map; } if (!oMap || oMap.nodeName.toLowerCase() !== 'map') {return false;} this.mapname = oMap.name; this.mapid = oMap.id; var newareas = oMap.getElementsByTagName('area'); var shape, coords, href, alt, title, target, id; for (var i=0, le = newareas.length; i 5) { window.setTimeout(function () {_this._setopacity(area, null, '-'+parts[1]);}, 20); pct = 1*curr + 5; } else if (diff < -3) { window.setTimeout(function () {_this._setopacity(area, null, '-'+parts[1]);}, 20); pct = 1*curr - 3; } else { //final set pct = parts[1]; } } } if (!isNaN(pct)) { pct = Math.round(parseInt(pct, 10)); //this.log('set ('+area.aid+'): ' + pct, 1); area.style.opacity = pct / 100; area.style.filter = 'alpha(opacity='+pct+')'; } }; /** * Get the currently set opacity of a given area. * @author adam * @param area The area (canvas) you want to get opacity info from. * @return Opacity value in a range of 0-100. */ imgmap.prototype._getopacity = function(area) { if (area.style.opacity <= 1) { return area.style.opacity * 100; } if (area.style.filter) { //alpha(opacity=NaN) return parseInt(area.style.filter.replace(/alpha\(opacity\=([^\)]*)\)/ig, "$1"), 10); } return 100;//default opacity }; /** * Removes the area marked by id. * removeAllAreas will indicate a mass flag so that the output HTML will only be updated at * the end of the operation. * Callback will call the GUI code to remove GUI elements. * @author Adam Maschek (adam.maschek(at)gmail.com) * @date 11-02-2007 20:40:58 * @param id The id of the area to remove. * @param mass Flag to indicate skipping the call of onHtmlChanged callback * @see #removeAllAreas */ imgmap.prototype.removeArea = function(id, mass) { if (this.viewmode === 1) {return;}//exit if preview mode if (id === null || typeof id == "undefined") {return;}//exit if no id given try { //remove area and label //explicitly set some values to null to avoid IE circular reference memleak this.areas[id].label.parentNode.removeChild(this.areas[id].label); this.areas[id].parentNode.removeChild(this.areas[id]); this.areas[id].label.className = null; //this.areas[id].label.style = null; //console.log(this.areas[id].label); this.areas[id].label = null; this.areas[id].onmouseover = null; this.areas[id].onmouseout = null; this.areas[id].onmouseup = null; this.areas[id].onmousedown = null; this.areas[id].onmousemove = null; // console.log(this.areas[id].label); } catch (err) { //alert('noparent'); } this.areas[id] = null; this.fireEvent('onRemoveArea', id); //update grand html if (!mass) {this.fireEvent('onHtmlChanged', this.getMapHTML());} }; /** * Removes all areas. * Will call removeArea on all areas. * @author Adam Maschek (adam.maschek(at)gmail.com) * @date 2006-06-07 11:55:34 * @see #removeArea */ imgmap.prototype.removeAllAreas = function() { for (var i = 0, le = this.areas.length; i < le; i++) { if (this.areas[i]) { this.removeArea(i, true); } } //only call this at the end, use mass param above to avoid calling it in process this.fireEvent('onHtmlChanged', this.getMapHTML()); }; /** * Scales all areas. * Will store scale parameter in globalscale property. * This is needed to know how to draw new areas on an already scaled canvas. * @author adam * @date 02-11-2008 14:13:14 * @param scale Scale factor (1-original, 0.5-half, 2-double, etc.) */ imgmap.prototype.scaleAllAreas = function(scale) { var rscale = 1;//relative scale try { rscale = scale / this.globalscale; } catch (err) { this.log("Invalid (global)scale", 1); } //console.log('gscale: '+this.globalscale); //console.log('scale: '+scale); //console.log('rscale: '+rscale); this.globalscale = scale; for (var i = 0, le = this.areas.length; i < le; i++) { if (this.areas[i] && this.areas[i].shape != 'undefined') { this.scaleArea(i, rscale); } } }; /** * Scales one area. * @author adam * @date 02-11-2008 14:13:14 * @param rscale Relative scale factor (1-keep, 0.5-half, 2-double, etc.) */ imgmap.prototype.scaleArea = function(id, rscale) { //set position and new dimensions this.areas[id].style.top = parseInt(this.areas[id].style.top, 10) * rscale + 'px'; this.areas[id].style.left = parseInt(this.areas[id].style.left, 10) * rscale + 'px'; this.setAreaSize(id, this.areas[id].width * rscale, this.areas[id].height * rscale); //handle polygon/bezier coordinates scaling if (this.areas[id].shape == 'poly' || this.areas[id].shape == 'bezier1') { for (var i=0, le = this.areas[id].xpoints.length; i 1) { //drawing point - draw a curve to it using the previous control point ctx.quadraticCurveTo(area.xpoints[area.xpoints.length - 1] - left - 5 , area.ypoints[area.ypoints.length - 1] - top - 5, x - left - 5 , y - top - 5); } else { //control point - simply draw a line to it ctx.lineTo(x - left - 5 , y - top - 5); } } //close area by drawing a line to the first point ctx.lineTo(area.xpoints[0] - left , area.ypoints[0] - top); ctx.stroke(); ctx.closePath(); } //put label this._putlabel(area.aid); this._puthint(area.aid); } }; /** * Updates Area coordinates. * Called when needed, eg. on mousemove, mousedown. * Also updates html container value (thru hook). * Calls callback onAreaChanged and onHtmlChanged so that GUI can follow. * This is an important hook to your GUI. * Uses globalscale to scale real coordinates to area coordinates. * @date 2006.10.24. 22:39:27 * @author Adam Maschek (adam.maschek(at)gmail.com) * @param id The id of the area. */ imgmap.prototype._updatecoords = function(id) { var left = Math.round(parseInt(this.areas[id].style.left, 10) / this.globalscale); var top = Math.round(parseInt(this.areas[id].style.top, 10) / this.globalscale); var height = Math.round(parseInt(this.areas[id].style.height, 10) / this.globalscale); var width = Math.round(parseInt(this.areas[id].style.width, 10) / this.globalscale); var value = ''; if (this.areas[id].shape == 'rect') { value = left + ',' + top + ',' + (left + width) + ',' + (top + height); this.areas[id].lastInput = value; } else if (this.areas[id].shape == 'circle') { var radius = Math.floor(width/2) - 1; value = (left + radius) + ',' + (top + radius) + ',' + radius; this.areas[id].lastInput = value; } else if (this.areas[id].shape == 'poly' || this.areas[id].shape == 'bezier1') { if (this.areas[id].xpoints) { for (var i=0, le = this.areas[id].xpoints.length; i parseInt(parts[2], 10) || parseInt(parts[1], 10) > parseInt(parts[3], 10)) {throw "invalid coords";} this.areas[id].style.left = this.globalscale * (this.pic.offsetLeft + parseInt(parts[0], 10)) + 'px'; this.areas[id].style.top = this.globalscale * (this.pic.offsetTop + parseInt(parts[1], 10)) + 'px'; this.setAreaSize(id, this.globalscale * (parts[2] - parts[0]), this.globalscale * (parts[3] - parts[1])); this._repaint(this.areas[id], this.config.CL_NORM_SHAPE); } else if (this.areas[id].shape == 'circle') { if (parts.length != 3 || parseInt(parts[2], 10) < 0) {throw "invalid coords";} var width = 2 * (parts[2]); //alert(parts[2]); //alert(width); this.setAreaSize(id, this.globalscale * width, this.globalscale * width); this.areas[id].style.left = this.globalscale * (this.pic.offsetLeft + parseInt(parts[0], 10) - width/2) + 'px'; this.areas[id].style.top = this.globalscale * (this.pic.offsetTop + parseInt(parts[1], 10) - width/2) + 'px'; this._repaint(this.areas[id], this.config.CL_NORM_SHAPE); } else if (this.areas[id].shape == 'poly' || this.areas[id].shape == 'bezier1') { if (parts.length < 2) {throw "invalid coords";} this.areas[id].xpoints = []; this.areas[id].ypoints = []; for (var i=0, le = parts.length; i parseInt(area.style.left, 10) + parseInt(area.style.width, 10)) { this.setAreaSize(area.aid, newx - parseInt(area.style.left, 10) + pad2, null); } if (newy < parseInt(area.style.top, 10)) { area.style.top = (newy - pad) + 'px'; this.setAreaSize(area.aid, null, parseInt(area.style.height, 10) + Math.abs(ydiff) + pad2); } else if (newy > parseInt(area.style.top, 10) + parseInt(area.style.height, 10)) { this.setAreaSize(area.aid, null, newy - parseInt(area.style.top, 10) + pad2); } }; /** * Shrink the polygon bounding area to the necessary size, by first reducing it * to the minimum, and then gradually growing it. * We need this because while we were drawing the polygon, it might have expanded * the canvas more than needed. * Will repaint the area. * @author adam * @param area The area to shrink. * @see #_polygongrow */ imgmap.prototype._polygonshrink = function(area) { //this.log('pshrink'); area.style.left = (area.xpoints[0]) + 'px'; area.style.top = (area.ypoints[0]) + 'px'; this.setAreaSize(area.aid, 0, 0); for (var i=0, le = area.xpoints.length; ithis.pic.width || y>this.pic.height) {return;} //old dimensions that need to be updated in this function if (this.memory[this.currentid]) { var top = this.memory[this.currentid].top; var left = this.memory[this.currentid].left; var height = this.memory[this.currentid].height; var width = this.memory[this.currentid].width; } // Handle shift state for Safari // Safari doesn't generate keyboard events for modifiers: http://bugs.webkit.org/show_bug.cgi?id=11696 if (this.isSafari) { if (e.shiftKey) { if (this.is_drawing == this.DM_RECTANGLE_DRAW) { this.is_drawing = this.DM_SQUARE_DRAW; this.statusMessage(this.strings.SQUARE2_DRAW); } } else { if (this.is_drawing == this.DM_SQUARE_DRAW && this.areas[this.currentid].shape == 'rect') { //not for circle! this.is_drawing = this.DM_RECTANGLE_DRAW; this.statusMessage(this.strings.RECTANGLE_DRAW); } } } if (this.is_drawing == this.DM_RECTANGLE_DRAW) { //rectangle mode this.fireEvent('onDrawArea', this.currentid); xdiff = x - this.memory[this.currentid].downx; ydiff = y - this.memory[this.currentid].downy; //alert(xdiff); this.setAreaSize(this.currentid, Math.abs(xdiff), Math.abs(ydiff)); if (xdiff < 0) { this.areas[this.currentid].style.left = (x + 1) + 'px'; } if (ydiff < 0) { this.areas[this.currentid].style.top = (y + 1) + 'px'; } } else if (this.is_drawing == this.DM_SQUARE_DRAW) { //square mode - align to shorter side this.fireEvent('onDrawArea', this.currentid); xdiff = x - this.memory[this.currentid].downx; ydiff = y - this.memory[this.currentid].downy; if (Math.abs(xdiff) < Math.abs(ydiff)) { diff = Math.abs(parseInt(xdiff, 10)); } else { diff = Math.abs(parseInt(ydiff, 10)); } //alert(xdiff); this.setAreaSize(this.currentid, diff, diff); if (xdiff < 0) { this.areas[this.currentid].style.left = (this.memory[this.currentid].downx + diff*-1) + 'px'; } if (ydiff < 0) { this.areas[this.currentid].style.top = (this.memory[this.currentid].downy + diff*-1 + 1) + 'px'; } } else if (this.is_drawing == this.DM_POLYGON_DRAW || this.is_drawing == this.DM_BEZIER_DRAW) { //polygon or bezier mode this.fireEvent('onDrawArea', this.currentid); this._polygongrow(this.areas[this.currentid], x, y); } else if (this.is_drawing == this.DM_RECTANGLE_MOVE || this.is_drawing == this.DM_SQUARE_MOVE) { this.fireEvent('onMoveArea', this.currentid); x = x - this.memory[this.currentid].rdownx; y = y - this.memory[this.currentid].rdowny; if (x + width > this.pic.width || y + height > this.pic.height) {return;} if (x < 0 || y < 0) {return;} //this.log(x + ' - '+width+ '+'+this.memory[this.currentid].rdownx +'='+xdiff ); this.areas[this.currentid].style.left = x + 1 + 'px'; this.areas[this.currentid].style.top = y + 1 + 'px'; } else if (this.is_drawing == this.DM_POLYGON_MOVE || this.is_drawing == this.DM_BEZIER_MOVE) { this.fireEvent('onMoveArea', this.currentid); x = x - this.memory[this.currentid].rdownx; y = y - this.memory[this.currentid].rdowny; if (x + width > this.pic.width || y + height > this.pic.height) {return;} if (x < 0 || y < 0) {return;} xdiff = x - left; ydiff = y - top; if (this.areas[this.currentid].xpoints) { for (var i=0, le = this.areas[this.currentid].xpoints.length; i 0) { //real resize left this.areas[this.currentid].style.left = x + 1 + 'px'; this.areas[this.currentid].style.top = (top + (diff/2)) + 'px'; this.setAreaSize(this.currentid, parseInt(width + (-1 * diff), 10), parseInt(height + (-1 * diff), 10)); } else { //jump to another state this.memory[this.currentid].width = 0; this.memory[this.currentid].height = 0; this.memory[this.currentid].left = x; this.memory[this.currentid].top = y; this.is_drawing = this.DM_SQUARE_RESIZE_RIGHT; } } else if (this.is_drawing == this.DM_SQUARE_RESIZE_RIGHT) { this.fireEvent('onResizeArea', this.currentid); diff = x - left - width; if ((width + (diff)) - 1 > 0) { //real resize right this.areas[this.currentid].style.top = (top + (-1* diff/2)) + 'px'; this.setAreaSize(this.currentid, (width + (diff)) - 1, (height + (diff))); } else { //jump to another state this.memory[this.currentid].width = 0; this.memory[this.currentid].height = 0; this.memory[this.currentid].left = x; this.memory[this.currentid].top = y; this.is_drawing = this.DM_SQUARE_RESIZE_LEFT; } } else if (this.is_drawing == this.DM_SQUARE_RESIZE_TOP) { this.fireEvent('onResizeArea', this.currentid); diff = y - top; if ((width + (-1 * diff)) > 0) { //real resize top this.areas[this.currentid].style.top = y + 1 + 'px'; this.areas[this.currentid].style.left = (left + (diff/2)) + 'px'; this.setAreaSize(this.currentid, (width + (-1 * diff)), (height + (-1 * diff))); } else { //jump to another state this.memory[this.currentid].width = 0; this.memory[this.currentid].height = 0; this.memory[this.currentid].left = x; this.memory[this.currentid].top = y; this.is_drawing = this.DM_SQUARE_RESIZE_BOTTOM; } } else if (this.is_drawing == this.DM_SQUARE_RESIZE_BOTTOM) { this.fireEvent('onResizeArea', this.currentid); diff = y - top - height; if ((width + (diff)) - 1 > 0) { //real resize bottom this.areas[this.currentid].style.left = (left + (-1* diff/2)) + 'px'; this.setAreaSize(this.currentid, (width + (diff)) - 1 , (height + (diff)) - 1); } else { //jump to another state this.memory[this.currentid].width = 0; this.memory[this.currentid].height = 0; this.memory[this.currentid].left = x; this.memory[this.currentid].top = y; this.is_drawing = this.DM_SQUARE_RESIZE_TOP; } } else if (this.is_drawing == this.DM_RECTANGLE_RESIZE_LEFT) { this.fireEvent('onResizeArea', this.currentid); xdiff = x - left; if (width + (-1 * xdiff) > 0) { //real resize left this.areas[this.currentid].style.left = x + 1 + 'px'; this.setAreaSize(this.currentid, width + (-1 * xdiff), null); } else { //jump to another state this.memory[this.currentid].width = 0; this.memory[this.currentid].left = x; this.is_drawing = this.DM_RECTANGLE_RESIZE_RIGHT; } } else if (this.is_drawing == this.DM_RECTANGLE_RESIZE_RIGHT) { this.fireEvent('onResizeArea', this.currentid); xdiff = x - left - width; if ((width + (xdiff)) - 1 > 0) { //real resize right this.setAreaSize(this.currentid, (width + (xdiff)) - 1, null); } else { //jump to another state this.memory[this.currentid].width = 0; this.memory[this.currentid].left = x; this.is_drawing = this.DM_RECTANGLE_RESIZE_LEFT; } } else if (this.is_drawing == this.DM_RECTANGLE_RESIZE_TOP) { this.fireEvent('onResizeArea', this.currentid); ydiff = y - top; if ((height + (-1 * ydiff)) > 0) { //real resize top this.areas[this.currentid].style.top = y + 1 + 'px'; this.setAreaSize(this.currentid, null, (height + (-1 * ydiff))); } else { //jump to another state this.memory[this.currentid].height = 0; this.memory[this.currentid].top = y; this.is_drawing = this.DM_RECTANGLE_RESIZE_BOTTOM; } } else if (this.is_drawing == this.DM_RECTANGLE_RESIZE_BOTTOM) { this.fireEvent('onResizeArea', this.currentid); ydiff = y - top - height; if ((height + (ydiff)) - 1 > 0) { //real resize bottom this.setAreaSize(this.currentid, null, (height + (ydiff)) - 1); } else { //jump to another state this.memory[this.currentid].height = 0; this.memory[this.currentid].top = y; this.is_drawing = this.DM_RECTANGLE_RESIZE_TOP; } } //repaint canvas elements if (this.is_drawing) { this._repaint(this.areas[this.currentid], this.config.CL_DRAW_SHAPE, x, y); this._updatecoords(this.currentid); } }; /** * EVENT HANDLER: Handles mouseup on the image. * Handles dragging and resizing. * @param e The event object. */ imgmap.prototype.img_mouseup = function(e) { if (this.viewmode === 1) {return;}//exit if preview mode //console.log('img_mouseup'); //if (!this.props[this.currentid]) return; var pos = this._getPos(this.pic); // 3.3.3 fix var x = (this.isMSIE) ? (window.event.x - this.pic.offsetLeft) : (e.clientX - pos.x); var y = (this.isMSIE) ? (window.event.y - this.pic.offsetTop) : (e.clientY - pos.y); x = x + this.pic_container.scrollLeft; y = y + this.pic_container.scrollTop; //for everything that is move or resize if (this.is_drawing != this.DM_RECTANGLE_DRAW && this.is_drawing != this.DM_SQUARE_DRAW && this.is_drawing != this.DM_POLYGON_DRAW && this.is_drawing != this.DM_POLYGON_LASTDRAW && this.is_drawing != this.DM_BEZIER_DRAW && this.is_drawing != this.DM_BEZIER_LASTDRAW) { //end dragging this.draggedId = null; //finish state this.is_drawing = 0; this.statusMessage(this.strings.READY); this.relaxArea(this.currentid); if (this.areas[this.currentid] == this._getLastArea()) { //if (this.config.mode != "editor2") this.addNewArea(); return; } this.memory[this.currentid].downx = x; this.memory[this.currentid].downy = y; } }; /** * EVENT HANDLER: Handles mousedown on the image. * Handles beggining or end of draw, or polygon/bezier point set. * @param e The event object. */ imgmap.prototype.img_mousedown = function(e) { if (this.viewmode === 1) {return;}//exit if preview mode if (!this.areas[this.currentid] && this.config.mode != "editor2") {return;} //console.log('img_mousedown'); var pos = this._getPos(this.pic); // 3.3.3 fix var x = (this.isMSIE) ? (window.event.x - this.pic.offsetLeft) : (e.clientX - pos.x); var y = (this.isMSIE) ? (window.event.y - this.pic.offsetTop) : (e.clientY - pos.y); x = x + this.pic_container.scrollLeft; y = y + this.pic_container.scrollTop; // Handle the Shift state if (!e) { e = window.event; } if (e.shiftKey) { if (this.is_drawing == this.DM_POLYGON_DRAW) { this.is_drawing = this.DM_POLYGON_LASTDRAW; } else if (this.is_drawing == this.DM_BEZIER_DRAW) { this.is_drawing = this.DM_BEZIER_LASTDRAW; } } //console.log(this.is_drawing); //this.statusMessage(x + ' - ' + y + ': ' + this.props[this.currentid].getElementsByTagName('select')[0].value); if (this.is_drawing == this.DM_POLYGON_DRAW || this.is_drawing == this.DM_BEZIER_DRAW) { //its not finish state yet this.areas[this.currentid].xpoints[this.areas[this.currentid].xpoints.length] = x - 5; this.areas[this.currentid].ypoints[this.areas[this.currentid].ypoints.length] = y - 5; this.memory[this.currentid].downx = x; this.memory[this.currentid].downy = y; return; } else if (this.is_drawing && this.is_drawing != this.DM_POLYGON_DRAW && this.is_drawing != this.DM_BEZIER_DRAW) { //finish any other state if (this.is_drawing == this.DM_POLYGON_LASTDRAW || this.is_drawing == this.DM_BEZIER_LASTDRAW) { //add last controlpoint and update coords this.areas[this.currentid].xpoints[this.areas[this.currentid].xpoints.length] = x - 5; this.areas[this.currentid].ypoints[this.areas[this.currentid].ypoints.length] = y - 5; this._updatecoords(this.currentid); this.is_drawing = 0; this._polygonshrink(this.areas[this.currentid]); } this.is_drawing = 0; this.statusMessage(this.strings.READY); this.relaxArea(this.currentid); if (this.areas[this.currentid] == this._getLastArea()) { //editor mode adds next area automatically if (this.config.mode != "editor2") {this.addNewArea();} return; } return; } if (this.config.mode == "editor2") { if (!this.nextShape) {return;} this.addNewArea(); //console.log("init: " + this.nextShape); this.initArea(this.currentid, this.nextShape); } else if (this.areas[this.currentid].shape == 'undefined' || this.areas[this.currentid].shape == 'poly') { //var shape = (this.props[this.currentid]) ? this.props[this.currentid].getElementsByTagName('select')[0].value : this.nextShape; var shape = this.nextShape; if (!shape) {shape = 'rect';} //console.log("init: " + shape); this.initArea(this.currentid, shape); } if (this.areas[this.currentid].shape == 'poly') { this.is_drawing = this.DM_POLYGON_DRAW; this.statusMessage(this.strings.POLYGON_DRAW); this.areas[this.currentid].style.left = x + 'px'; this.areas[this.currentid].style.top = y + 'px'; this.areas[this.currentid].style.width = 0; this.areas[this.currentid].style.height = 0; this.areas[this.currentid].xpoints = []; this.areas[this.currentid].ypoints = []; this.areas[this.currentid].xpoints[0] = x; this.areas[this.currentid].ypoints[0] = y; } else if (this.areas[this.currentid].shape == 'bezier1') { this.is_drawing = this.DM_BEZIER_DRAW; this.statusMessage(this.strings.BEZIER_DRAW); this.areas[this.currentid].style.left = x + 'px'; this.areas[this.currentid].style.top = y + 'px'; this.areas[this.currentid].style.width = 0; this.areas[this.currentid].style.height = 0; this.areas[this.currentid].xpoints = []; this.areas[this.currentid].ypoints = []; this.areas[this.currentid].xpoints[0] = x; this.areas[this.currentid].ypoints[0] = y; } else if (this.areas[this.currentid].shape == 'rect') { this.is_drawing = this.DM_RECTANGLE_DRAW; this.statusMessage(this.strings.RECTANGLE_DRAW); this.areas[this.currentid].style.left = x + 'px'; this.areas[this.currentid].style.top = y + 'px'; this.areas[this.currentid].style.width = 0; this.areas[this.currentid].style.height = 0; } else if (this.areas[this.currentid].shape == 'circle') { this.is_drawing = this.DM_SQUARE_DRAW; this.statusMessage(this.strings.SQUARE_DRAW); this.areas[this.currentid].style.left = x + 'px'; this.areas[this.currentid].style.top = y + 'px'; this.areas[this.currentid].style.width = 0; this.areas[this.currentid].style.height = 0; } this._setBorder(this.currentid, 'DRAW'); this.memory[this.currentid].downx = x; this.memory[this.currentid].downy = y; }; /** * Highlights a given area. * Sets opacity and repaints. * @date 2007.12.28. 18:23:00 * @param id The id of the area to blur. * @param flag Modifier, possible values: grad - for gradual fade in */ imgmap.prototype.highlightArea = function(id, flag) { if (this.is_drawing) {return;}//exit if in drawing state if (this.areas[id] && this.areas[id].shape != 'undefined') { //area exists - highlight it this.fireEvent('onFocusArea', this.areas[id]); this._setBorder(id, 'HIGHLIGHT'); var opacity = this.config.highlight_opacity; if (flag == 'grad') { //apply gradient opacity opacity = '-' + opacity; } this._setopacity(this.areas[id], this.config.CL_HIGHLIGHT_BG, opacity); this._repaint(this.areas[id], this.config.CL_HIGHLIGHT_SHAPE); } }; /** * Blurs a given area. * Sets opacity and repaints. * @date 2007.12.28. 18:23:26 * @param id The id of the area to blur. * @param flag Modifier, possible values: grad - for gradual fade out */ imgmap.prototype.blurArea = function(id, flag) { if (this.is_drawing) {return;}//exit if in drawing state if (this.areas[id] && this.areas[id].shape != 'undefined') { //area exists - fade it back this.fireEvent('onBlurArea', this.areas[id]); this._setBorder(id, 'NORM'); var opacity = this.config.norm_opacity; if (flag == 'grad') { //apply gradient opacity opacity = '-' + opacity; } this._setopacity(this.areas[id], this.config.CL_NORM_BG, opacity); this._repaint(this.areas[id], this.config.CL_NORM_SHAPE); } }; /** * EVENT HANDLER: Handles event of mousemove on imgmap areas. * - changes cursor depending where we are inside the area (buggy in opera) * - handles area resize * - handles area move * @url http://evolt.org/article/Mission_Impossible_mouse_position/17/23335/index.html * @url http://my.opera.com/community/forums/topic.dml?id=239498&t=1217158015&page=1 * @author adam * @param e The event object. */ imgmap.prototype.area_mousemove = function(e) { if (this.viewmode === 1) {return;}//exit if preview mode if (!this.is_drawing) { var obj = (this.isMSIE) ? window.event.srcElement : e.currentTarget; if (obj.tagName == 'DIV') { //do this because of label obj = obj.parentNode; } if (obj.tagName == 'image' || obj.tagName == 'group' || obj.tagName == 'shape' || obj.tagName == 'stroke') { //do this because of excanvas obj = obj.parentNode.parentNode; } //opera fix - adam - 04-12-2007 23:14:05 if (this.isOpera) { e.layerX = e.offsetX; e.layerY = e.offsetY; } var xdiff = (this.isMSIE) ? (window.event.offsetX) : (e.layerX); var ydiff = (this.isMSIE) ? (window.event.offsetY) : (e.layerY); //this.log(obj.aid + ' : ' + xdiff + ',' + ydiff); var resizable = (obj.shape == 'rect' || obj.shape == 'circle'); if (resizable && xdiff < 6 && ydiff > 6) { //move left obj.style.cursor = 'w-resize'; } else if (resizable && xdiff > parseInt(obj.style.width, 10) - 6 && ydiff > 6) { //move right obj.style.cursor = 'e-resize'; } else if (resizable && xdiff > 6 && ydiff < 6) { //move top obj.style.cursor = 'n-resize'; } else if (resizable && ydiff > parseInt(obj.style.height, 10) - 6 && xdiff > 6) { //move bottom obj.style.cursor = 's-resize'; } else { //move all obj.style.cursor = 'move'; } if (obj.aid != this.draggedId) { //not dragged or different if (obj.style.cursor == 'move') {obj.style.cursor = 'default';} return; } //moved here from mousedown if (xdiff < 6 && ydiff > 6) { //move left if (this.areas[this.currentid].shape == 'circle') { this.is_drawing = this.DM_SQUARE_RESIZE_LEFT; this.statusMessage(this.strings.SQUARE_RESIZE_LEFT); } else if (this.areas[this.currentid].shape == 'rect') { this.is_drawing = this.DM_RECTANGLE_RESIZE_LEFT; this.statusMessage(this.strings.RECTANGLE_RESIZE_LEFT); } } else if (xdiff > parseInt(this.areas[this.currentid].style.width, 10) - 6 && ydiff > 6) { //move right if (this.areas[this.currentid].shape == 'circle') { this.is_drawing = this.DM_SQUARE_RESIZE_RIGHT; this.statusMessage(this.strings.SQUARE_RESIZE_RIGHT); } else if (this.areas[this.currentid].shape == 'rect') { this.is_drawing = this.DM_RECTANGLE_RESIZE_RIGHT; this.statusMessage(this.strings.RECTANGLE_RESIZE_RIGHT); } } else if (xdiff > 6 && ydiff < 6) { //move top if (this.areas[this.currentid].shape == 'circle') { this.is_drawing = this.DM_SQUARE_RESIZE_TOP; this.statusMessage(this.strings.SQUARE_RESIZE_TOP); } else if (this.areas[this.currentid].shape == 'rect') { this.is_drawing = this.DM_RECTANGLE_RESIZE_TOP; this.statusMessage(this.strings.RECTANGLE_RESIZE_TOP); } } else if (ydiff > parseInt(this.areas[this.currentid].style.height, 10) - 6 && xdiff > 6) { //move bottom if (this.areas[this.currentid].shape == 'circle') { this.is_drawing = this.DM_SQUARE_RESIZE_BOTTOM; this.statusMessage(this.strings.SQUARE_RESIZE_BOTTOM); } else if (this.areas[this.currentid].shape == 'rect') { this.is_drawing = this.DM_RECTANGLE_RESIZE_BOTTOM; this.statusMessage(this.strings.RECTANGLE_RESIZE_BOTTOM); } } else/*if (xdiff < 10 && ydiff < 10 ) */{ //move all if (this.areas[this.currentid].shape == 'circle') { this.is_drawing = this.DM_SQUARE_MOVE; this.statusMessage(this.strings.SQUARE_MOVE); this.memory[this.currentid].rdownx = xdiff; this.memory[this.currentid].rdowny = ydiff; } else if (this.areas[this.currentid].shape == 'rect') { this.is_drawing = this.DM_RECTANGLE_MOVE; this.statusMessage(this.strings.RECTANGLE_MOVE); this.memory[this.currentid].rdownx = xdiff; this.memory[this.currentid].rdowny = ydiff; } else if (this.areas[this.currentid].shape == 'poly' || this.areas[this.currentid].shape == 'bezier1') { if (this.areas[this.currentid].xpoints) { for (var i=0, le = this.areas[this.currentid].xpoints.length; i 0) {xpos += element.offsetLeft;} if (element.offsetTop > 0) {ypos += element.offsetTop;} element = elementOffsetParent; } // handle positioned element in Chrome if (element.offsetLeft > 0) {xpos += element.offsetLeft;} if (element.offsetTop > 0) {ypos += element.offsetTop;} } else { xpos = element.offsetLeft; ypos = element.offsetTop; } } return {x: xpos, y: ypos}; }; /** * Gets the last (visible and editable) area. * @author Adam Maschek (adam.maschek(at)gmail.com) * @date 2006-06-15 16:34:51 * @returns The last area object or null. */ imgmap.prototype._getLastArea = function() { for (var i = this.areas.length-1; i>=0; i--) { if (this.areas[i]) { return this.areas[i]; } } return null; }; /** * Parses cssText to single style declarations. * @author adam * @date 25-09-2007 18:19:51 * @param obj The DOM object to apply styles on. * @param cssText The css declarations to apply. */ imgmap.prototype.assignCSS = function(obj, cssText) { var parts = cssText.split(';'); for (var i = 0; i < parts.length; i++) { var p = parts[i].split(':'); //we need to camelcase by - signs var pp = this.trim(p[0]).split('-'); var prop = pp[0]; for (var j = 1; j < pp.length; j++) { //replace first letters to uppercase prop+= pp[j].replace(/^\w/, pp[j].substring(0,1).toUpperCase()); } obj.style[this.trim(prop)] = this.trim(p[1]); } }; /** * To fire callback hooks on custom events, passing them the object of the event. * @author adam * @date 13-10-2007 15:24:49 * @param evt The type of event * @param obj The object of the event. (can be an id, a string, an object, whatever is most relevant) */ imgmap.prototype.fireEvent = function(evt, obj) { //this.log("Firing event: " + evt); if (typeof this.config.custom_callbacks[evt] == 'function') { return this.config.custom_callbacks[evt](obj); } }; /** * To set area dimensions. * This is needed to achieve the same result in all browsers. * @author adam * @date 10-12-2007 22:29:41 * @param id The id of the area (canvas) to resize. * @param w The desired width in pixels. * @param h The desired height in pixels. */ imgmap.prototype.setAreaSize = function(id, w, h) { if (id === null) {id = this.currentid;} if (w !== null) { this.areas[id].width = w; this.areas[id].style.width = (w) + 'px'; this.areas[id].setAttribute('width', w); } if (h !== null) { this.areas[id].height = h; this.areas[id].style.height = (h) + 'px'; this.areas[id].setAttribute('height', h); } }; /** * Tries to detect preferred language of user. * @date 2007.12.28. 15:43:46 * @return The two byte language code. (We dont care now for pt-br, etc.) */ imgmap.prototype.detectLanguage = function() { var lang; if (navigator.userLanguage) { lang = navigator.userLanguage.toLowerCase(); } else if (navigator.language) { lang = navigator.language.toLowerCase(); } else { return this.config.defaultLang; } //this.log(lang, 2); if (lang.length >= 2) { lang = lang.substring(0,2); return lang; } return this.config.defaultLang; }; /** * Disable selection on a given object. * This is especially useful in Safari, where dragging around areas * keeps selecting all sorts of things. * @author Bret Taylor * @url http://ajaxcookbook.org/disable-text-selection/ * @date 27-07-2008 1:57:45 * @param element The DOM element on which you want to disable selection. */ imgmap.prototype.disableSelection = function(element) { if (typeof element == 'undefined' || !element) {return false;} if (typeof element.onselectstart != "undefined") { element.onselectstart = function() { return false; }; } if (typeof element.unselectable != "undefined") { element.unselectable = "on"; } if (typeof element.style.MozUserSelect != "undefined") { element.style.MozUserSelect = "none"; } }; /** * @date 11-02-2007 19:57:05 * @url http://www.deepwood.net/writing/method-references.html.utf8 * @author Daniel Brockman * @addon */ Function.prototype.bind = function(object) { var method = this; return function () { return method.apply(object, arguments); }; }; /** * Trims a string. * Changed not to extend String but use own function for better compatibility. * @param str The string to trim. */ imgmap.prototype.trim = function(str) { return str.replace(/^\s+|\s+$/g, ''); }; /** * Spawn an imgmap object for each imagemap found in the document. * This is used for highlighter mode only. * @param config An imgmap config object */ function imgmap_spawnObjects(config) { //console.log('spawnobjects'); var maps = document.getElementsByTagName('map'); var imgs = document.getElementsByTagName('img'); var imaps = []; var imapn; //console.log(maps.length); for (var i=0, le=maps.length; i