/** * $Id: mxClient.js,v 1.204 2013-01-24 12:14:27 gaudenz Exp $ * Copyright (c) 2006-2010, JGraph Ltd */ var mxClient = { /** * Class: mxClient * * Bootstrapping mechanism for the mxGraph thin client. The production version * of this file contains all code required to run the mxGraph thin client, as * well as global constants to identify the browser and operating system in * use. You may have to load chrome://global/content/contentAreaUtils.js in * your page to disable certain security restrictions in Mozilla. * * Variable: VERSION * * Contains the current version of the mxGraph library. The strings that * communicate versions of mxGraph use the following format. * * versionMajor.versionMinor.buildNumber.revisionNumber * * Current version is 1.10.4.3. */ VERSION: '1.10.4.3', /** * Variable: IS_IE * * True if the current browser is Internet Explorer. */ IS_IE: navigator.userAgent.indexOf('MSIE') >= 0, /** * Variable: IS_IE6 * * True if the current browser is Internet Explorer 6.x. */ IS_IE6: navigator.userAgent.indexOf('MSIE 6') >= 0, /** * Variable: IS_QUIRKS * * True if the current browser is Internet Explorer and it is in quirks mode. */ IS_QUIRKS: navigator.userAgent.indexOf('MSIE') >= 0 && (document.documentMode == null || document.documentMode == 5), /** * Variable: IS_NS * * True if the current browser is Netscape (including Firefox). */ IS_NS: navigator.userAgent.indexOf('Mozilla/') >= 0 && navigator.userAgent.indexOf('MSIE') < 0, /** * Variable: IS_OP * * True if the current browser is Opera. */ IS_OP: navigator.userAgent.indexOf('Opera/') >= 0, /** * Variable: IS_OT * * True if -o-transform is available as a CSS style. This is the case * for Opera browsers that use Presto/2.5 and later. */ IS_OT: navigator.userAgent.indexOf('Presto/2.4.') < 0 && navigator.userAgent.indexOf('Presto/2.3.') < 0 && navigator.userAgent.indexOf('Presto/2.2.') < 0 && navigator.userAgent.indexOf('Presto/2.1.') < 0 && navigator.userAgent.indexOf('Presto/2.0.') < 0 && navigator.userAgent.indexOf('Presto/1.') < 0, /** * Variable: IS_SF * * True if the current browser is Safari. */ IS_SF: navigator.userAgent.indexOf('AppleWebKit/') >= 0 && navigator.userAgent.indexOf('Chrome/') < 0, /** * Variable: IS_GC * * True if the current browser is Google Chrome. */ IS_GC: navigator.userAgent.indexOf('Chrome/') >= 0, /** * Variable: IS_MT * * True if -moz-transform is available as a CSS style. This is the case * for all Firefox-based browsers newer than or equal 3, such as Camino, * Iceweasel, Seamonkey and Iceape. */ IS_MT: (navigator.userAgent.indexOf('Firefox/') >= 0 && navigator.userAgent.indexOf('Firefox/1.') < 0 && navigator.userAgent.indexOf('Firefox/2.') < 0) || (navigator.userAgent.indexOf('Iceweasel/') >= 0 && navigator.userAgent.indexOf('Iceweasel/1.') < 0 && navigator.userAgent.indexOf('Iceweasel/2.') < 0) || (navigator.userAgent.indexOf('SeaMonkey/') >= 0 && navigator.userAgent.indexOf('SeaMonkey/1.') < 0) || (navigator.userAgent.indexOf('Iceape/') >= 0 && navigator.userAgent.indexOf('Iceape/1.') < 0), /** * Variable: IS_SVG * * True if the browser supports SVG. */ IS_SVG: navigator.userAgent.indexOf('Firefox/') >= 0 || // FF and Camino navigator.userAgent.indexOf('Iceweasel/') >= 0 || // Firefox on Debian navigator.userAgent.indexOf('Seamonkey/') >= 0 || // Firefox-based navigator.userAgent.indexOf('Iceape/') >= 0 || // Seamonkey on Debian navigator.userAgent.indexOf('Galeon/') >= 0 || // Gnome Browser (old) navigator.userAgent.indexOf('Epiphany/') >= 0 || // Gnome Browser (new) navigator.userAgent.indexOf('AppleWebKit/') >= 0 || // Safari/Google Chrome navigator.userAgent.indexOf('Gecko/') >= 0 || // Netscape/Gecko navigator.userAgent.indexOf('Opera/') >= 0, /** * Variable: NO_FO * * True if foreignObject support is not available. This is the case for * Opera and older SVG-based browsers. IE does not require this type * of tag. */ NO_FO: navigator.userAgent.indexOf('Firefox/1.') >= 0 || navigator.userAgent.indexOf('Iceweasel/1.') >= 0 || navigator.userAgent.indexOf('Firefox/2.') >= 0 || navigator.userAgent.indexOf('Iceweasel/2.') >= 0 || navigator.userAgent.indexOf('SeaMonkey/1.') >= 0 || navigator.userAgent.indexOf('Iceape/1.') >= 0 || navigator.userAgent.indexOf('Camino/1.') >= 0 || navigator.userAgent.indexOf('Epiphany/2.') >= 0 || navigator.userAgent.indexOf('Opera/') >= 0 || navigator.userAgent.indexOf('MSIE') >= 0 || navigator.userAgent.indexOf('Mozilla/2.') >= 0, // Safari/Google Chrome /** * Variable: IS_VML * * True if the browser supports VML. */ IS_VML: navigator.appName.toUpperCase() == 'MICROSOFT INTERNET EXPLORER', /** * Variable: IS_MAC * * True if the client is a Mac. */ IS_MAC: navigator.userAgent.toUpperCase().indexOf('MACINTOSH') > 0, /** * Variable: IS_TOUCH * * True if this client uses a touch interface (no mouse). Currently this * detects IPads, IPods, IPhones and Android devices. */ IS_TOUCH: navigator.userAgent.toUpperCase().indexOf('IPAD') > 0 || navigator.userAgent.toUpperCase().indexOf('IPOD') > 0 || navigator.userAgent.toUpperCase().indexOf('IPHONE') > 0 || navigator.userAgent.toUpperCase().indexOf('ANDROID') > 0, /** * Variable: IS_LOCAL * * True if the documents location does not start with http:// or https://. */ IS_LOCAL: document.location.href.indexOf('http://') < 0 && document.location.href.indexOf('https://') < 0, /** * Function: isBrowserSupported * * Returns true if the current browser is supported, that is, if * or is true. * * Example: * * (code) * if (!mxClient.isBrowserSupported()) * { * mxUtils.error('Browser is not supported!', 200, false); * } * (end) */ isBrowserSupported: function() { return mxClient.IS_VML || mxClient.IS_SVG; }, /** * Function: link * * Adds a link node to the head of the document. Use this * to add a stylesheet to the page as follows: * * (code) * mxClient.link('stylesheet', filename); * (end) * * where filename is the (relative) URL of the stylesheet. The charset * is hardcoded to ISO-8859-1 and the type is text/css. * * Parameters: * * rel - String that represents the rel attribute of the link node. * href - String that represents the href attribute of the link node. * doc - Optional parent document of the link node. */ link: function(rel, href, doc) { doc = doc || document; // Workaround for Operation Aborted in IE6 if base tag is used in head if (mxClient.IS_IE6) { doc.write(''); } else { var link = doc.createElement('link'); link.setAttribute('rel', rel); link.setAttribute('href', href); link.setAttribute('charset', 'ISO-8859-1'); link.setAttribute('type', 'text/css'); var head = doc.getElementsByTagName('head')[0]; head.appendChild(link); } }, /** * Function: include * * Dynamically adds a script node to the document header. * * In production environments, the includes are resolved in the mxClient.js * file to reduce the number of requests required for client startup. This * function should only be used in development environments, but not in * production systems. */ include: function(src) { document.write(''); }, /** * Function: dispose * * Frees up memory in IE by resolving cyclic dependencies between the DOM * and the JavaScript objects. */ dispose: function() { // Cleans all objects where listeners have been added for (var i = 0; i < mxEvent.objects.length; i++) { if (mxEvent.objects[i].mxListenerList != null) { mxEvent.removeAllListeners(mxEvent.objects[i]); } } } }; /** * Variable: mxLoadResources * * Optional global config variable to toggle loading of the two resource files * in and . Default is true. NOTE: This is a global variable, * not a variable of mxClient. * * (code) * * * (end) */ if (typeof(mxLoadResources) == 'undefined') { mxLoadResources = true; } /** * Variable: mxLoadStylesheets * * Optional global config variable to toggle loading of the CSS files when * the library is initialized. Default is true. NOTE: This is a global variable, * not a variable of mxClient. * * (code) * * * (end) */ if (typeof(mxLoadStylesheets) == 'undefined') { mxLoadStylesheets = true; } /** * Variable: basePath * * Basepath for all URLs in the core without trailing slash. Default is '.'. * Set mxBasePath prior to loading the mxClient library as follows to override * this setting: * * (code) * * * (end) * * When using a relative path, the path is relative to the URL of the page that * contains the assignment. Trailing slashes are automatically removed. */ if (typeof(mxBasePath) != 'undefined' && mxBasePath.length > 0) { // Adds a trailing slash if required if (mxBasePath.substring(mxBasePath.length - 1) == '/') { mxBasePath = mxBasePath.substring(0, mxBasePath.length - 1); } mxClient.basePath = mxBasePath; } else { mxClient.basePath = '.'; } /** * Variable: imageBasePath * * Basepath for all images URLs in the core without trailing slash. Default is * + '/images'. Set mxImageBasePath prior to loading the * mxClient library as follows to override this setting: * * (code) * * * (end) * * When using a relative path, the path is relative to the URL of the page that * contains the assignment. Trailing slashes are automatically removed. */ if (typeof(mxImageBasePath) != 'undefined' && mxImageBasePath.length > 0) { // Adds a trailing slash if required if (mxImageBasePath.substring(mxImageBasePath.length - 1) == '/') { mxImageBasePath = mxImageBasePath.substring(0, mxImageBasePath.length - 1); } mxClient.imageBasePath = mxImageBasePath; } else { mxClient.imageBasePath = mxClient.basePath + '/images'; } /** * Variable: language * * Defines the language of the client, eg. en for english, de for german etc. * The special value 'none' will disable all built-in internationalization and * resource loading. See for handling identifiers * with and without a dash. * * Set mxLanguage prior to loading the mxClient library as follows to override * this setting: * * (code) * * * (end) * * If internationalization is disabled, then the following variables should be * overridden to reflect the current language of the system. These variables are * cleared when i18n is disabled. * , , * , , * , , , * , , * , , * , , * , , * and * . */ if (typeof(mxLanguage) != 'undefined') { mxClient.language = mxLanguage; } else { mxClient.language = (mxClient.IS_IE) ? navigator.userLanguage : navigator.language; } /** * Variable: defaultLanguage * * Defines the default language which is used in the common resource files. Any * resources for this language will only load the common resource file, but not * the language-specific resource file. Default is 'en'. * * Set mxDefaultLanguage prior to loading the mxClient library as follows to override * this setting: * * (code) * * * (end) */ if (typeof(mxDefaultLanguage) != 'undefined') { mxClient.defaultLanguage = mxDefaultLanguage; } else { mxClient.defaultLanguage = 'en'; } // Adds all required stylesheets and namespaces if (mxLoadStylesheets) { mxClient.link('stylesheet', mxClient.basePath + '/css/common.css'); } /** * Variable: languages * * Defines the optional array of all supported language extensions. The default * language does not have to be part of this list. See * . * * (code) * * * (end) * * This is used to avoid unnecessary requests to language files, ie. if a 404 * will be returned. */ if (typeof(mxLanguages) != 'undefined') { mxClient.languages = mxLanguages; } if (mxClient.IS_IE) { // IE9/10 standards mode uses SVG (VML is broken) if (document.documentMode >= 9) { mxClient.IS_VML = false; mxClient.IS_SVG = true; } else { // Enables support for IE8 standards mode. Note that this requires all attributes for VML // elements to be set using direct notation, ie. node.attr = value. The use of setAttribute // is not possible. See mxShape.init for more code to handle this specific document mode. if (document.documentMode == 8) { document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML'); document.namespaces.add('o', 'urn:schemas-microsoft-com:office:office', '#default#VML'); } else { document.namespaces.add('v', 'urn:schemas-microsoft-com:vml'); document.namespaces.add('o', 'urn:schemas-microsoft-com:office:office'); } var ss = document.createStyleSheet(); ss.cssText = 'v\\:*{behavior:url(#default#VML)}o\\:*{behavior:url(#default#VML)}'; if (mxLoadStylesheets) { mxClient.link('stylesheet', mxClient.basePath + '/css/explorer.css'); } } // Cleans up resources when the application terminates window.attachEvent('onunload', mxClient.dispose); } /** * $Id: mxLog.js,v 1.32 2012-11-12 09:40:59 gaudenz Exp $ * Copyright (c) 2006-2010, JGraph Ltd */ var mxLog = { /** * Class: mxLog * * A singleton class that implements a simple console. * * Variable: consoleName * * Specifies the name of the console window. Default is 'Console'. */ consoleName: 'Console', /** * Variable: TRACE * * Specified if the output for and should be visible in the * console. Default is false. */ TRACE: false, /** * Variable: DEBUG * * Specifies if the output for should be visible in the console. * Default is true. */ DEBUG: true, /** * Variable: WARN * * Specifies if the output for should be visible in the console. * Default is true. */ WARN: true, /** * Variable: buffer * * Buffer for pre-initialized content. */ buffer: '', /** * Function: init * * Initializes the DOM node for the console. This requires document.body to * point to a non-null value. This is called from within if the * log has not yet been initialized. */ init: function() { if (mxLog.window == null && document.body != null) { var title = mxLog.consoleName + ' - mxGraph ' + mxClient.VERSION; // Creates a table that maintains the layout var table = document.createElement('table'); table.setAttribute('width', '100%'); table.setAttribute('height', '100%'); var tbody = document.createElement('tbody'); var tr = document.createElement('tr'); var td = document.createElement('td'); td.style.verticalAlign = 'top'; // Adds the actual console as a textarea mxLog.textarea = document.createElement('textarea'); mxLog.textarea.setAttribute('readOnly', 'true'); mxLog.textarea.style.height = '100%'; mxLog.textarea.style.resize = 'none'; mxLog.textarea.value = mxLog.buffer; // Workaround for wrong width in standards mode if (mxClient.IS_NS && document.compatMode != 'BackCompat') { mxLog.textarea.style.width = '99%'; } else { mxLog.textarea.style.width = '100%'; } td.appendChild(mxLog.textarea); tr.appendChild(td); tbody.appendChild(tr); // Creates the container div tr = document.createElement('tr'); mxLog.td = document.createElement('td'); mxLog.td.style.verticalAlign = 'top'; mxLog.td.setAttribute('height', '30px'); tr.appendChild(mxLog.td); tbody.appendChild(tr); table.appendChild(tbody); // Adds various debugging buttons mxLog.addButton('Info', function (evt) { mxLog.info(); }); mxLog.addButton('DOM', function (evt) { var content = mxUtils.getInnerHtml(document.body); mxLog.debug(content); }); mxLog.addButton('Trace', function (evt) { mxLog.TRACE = !mxLog.TRACE; if (mxLog.TRACE) { mxLog.debug('Tracing enabled'); } else { mxLog.debug('Tracing disabled'); } }); mxLog.addButton('Copy', function (evt) { try { mxUtils.copy(mxLog.textarea.value); } catch (err) { mxUtils.alert(err); } }); mxLog.addButton('Show', function (evt) { try { mxUtils.popup(mxLog.textarea.value); } catch (err) { mxUtils.alert(err); } }); mxLog.addButton('Clear', function (evt) { mxLog.textarea.value = ''; }); // Cross-browser code to get window size var h = 0; var w = 0; if (typeof(window.innerWidth) === 'number') { h = window.innerHeight; w = window.innerWidth; } else { h = (document.documentElement.clientHeight || document.body.clientHeight); w = document.body.clientWidth; } mxLog.window = new mxWindow(title, table, Math.max(0, w-320), Math.max(0, h-210), 300, 160); mxLog.window.setMaximizable(true); mxLog.window.setScrollable(false); mxLog.window.setResizable(true); mxLog.window.setClosable(true); mxLog.window.destroyOnClose = false; // Workaround for ignored textarea height in various setups if ((mxClient.IS_NS || mxClient.IS_IE) && !mxClient.IS_GC && !mxClient.IS_SF && document.compatMode != 'BackCompat') { var elt = mxLog.window.getElement(); var resizeHandler = function(sender, evt) { mxLog.textarea.style.height = Math.max(0, elt.offsetHeight - 70)+'px'; }; mxLog.window.addListener(mxEvent.RESIZE_END, resizeHandler); mxLog.window.addListener(mxEvent.MAXIMIZE, resizeHandler); mxLog.window.addListener(mxEvent.NORMALIZE, resizeHandler); mxLog.textarea.style.height = '92px'; } } }, /** * Function: info * * Writes the current navigator information to the console. */ info: function() { mxLog.writeln(mxUtils.toString(navigator)); }, /** * Function: addButton * * Adds a button to the console using the given label and function. */ addButton: function(lab, funct) { var button = document.createElement('button'); mxUtils.write(button, lab); mxEvent.addListener(button, 'click', funct); mxLog.td.appendChild(button); }, /** * Function: isVisible * * Returns true if the console is visible. */ isVisible: function() { if (mxLog.window != null) { return mxLog.window.isVisible(); } return false; }, /** * Function: show * * Shows the console. */ show: function() { mxLog.setVisible(true); }, /** * Function: setVisible * * Shows or hides the console. */ setVisible: function(visible) { if (mxLog.window == null) { mxLog.init(); } if (mxLog.window != null) { mxLog.window.setVisible(visible); } }, /** * Function: enter * * Writes the specified string to the console * if is true and returns the current * time in milliseconds. * * Example: * * (code) * mxLog.show(); * var t0 = mxLog.enter('Hello'); * // Do something * mxLog.leave('World!', t0); * (end) */ enter: function(string) { if (mxLog.TRACE) { mxLog.writeln('Entering '+string); return new Date().getTime(); } }, /** * Function: leave * * Writes the specified string to the console * if is true and computes the difference * between the current time and t0 in milliseconds. * See for an example. */ leave: function(string, t0) { if (mxLog.TRACE) { var dt = (t0 != 0) ? ' ('+(new Date().getTime() - t0)+' ms)' : ''; mxLog.writeln('Leaving '+string+dt); } }, /** * Function: debug * * Adds all arguments to the console if is enabled. * * Example: * * (code) * mxLog.show(); * mxLog.debug('Hello, World!'); * (end) */ debug: function() { if (mxLog.DEBUG) { mxLog.writeln.apply(this, arguments); } }, /** * Function: warn * * Adds all arguments to the console if is enabled. * * Example: * * (code) * mxLog.show(); * mxLog.warn('Hello, World!'); * (end) */ warn: function() { if (mxLog.WARN) { mxLog.writeln.apply(this, arguments); } }, /** * Function: write * * Adds the specified strings to the console. */ write: function() { var string = ''; for (var i = 0; i < arguments.length; i++) { string += arguments[i]; if (i < arguments.length - 1) { string += ' '; } } if (mxLog.textarea != null) { mxLog.textarea.value = mxLog.textarea.value + string; // Workaround for no update in Presto 2.5.22 (Opera 10.5) if (navigator.userAgent.indexOf('Presto/2.5') >= 0) { mxLog.textarea.style.visibility = 'hidden'; mxLog.textarea.style.visibility = 'visible'; } mxLog.textarea.scrollTop = mxLog.textarea.scrollHeight; } else { mxLog.buffer += string; } }, /** * Function: writeln * * Adds the specified strings to the console, appending a linefeed at the * end of each string. */ writeln: function() { var string = ''; for (var i = 0; i < arguments.length; i++) { string += arguments[i]; if (i < arguments.length - 1) { string += ' '; } } mxLog.write(string + '\n'); } }; /** * $Id: mxObjectIdentity.js,v 1.8 2010-01-02 09:45:14 gaudenz Exp $ * Copyright (c) 2006-2010, JGraph Ltd */ var mxObjectIdentity = { /** * Class: mxObjectIdentity * * Identity for JavaScript objects. This is implemented using a simple * incremeting counter which is stored in each object under . * * The identity for an object does not change during its lifecycle. * * Variable: FIELD_NAME * * Name of the field to be used to store the object ID. Default is * '_mxObjectId'. */ FIELD_NAME: 'mxObjectId', /** * Variable: counter * * Current counter for objects. */ counter: 0, /** * Function: get * * Returns the object id for the given object. */ get: function(obj) { if (typeof(obj) == 'object' && obj[mxObjectIdentity.FIELD_NAME] == null) { var ctor = mxUtils.getFunctionName(obj.constructor); obj[mxObjectIdentity.FIELD_NAME] = ctor+'#'+mxObjectIdentity.counter++; } return obj[mxObjectIdentity.FIELD_NAME]; }, /** * Function: clear * * Removes the object id from the given object. */ clear: function(obj) { if (typeof(obj) == 'object') { delete obj[mxObjectIdentity.FIELD_NAME]; } } }; /** * $Id: mxDictionary.js,v 1.12 2012-04-26 08:08:54 gaudenz Exp $ * Copyright (c) 2006-2010, JGraph Ltd */ /** * Class: mxDictionary * * A wrapper class for an associative array with object keys. Note: This * implementation uses to turn object keys into strings. * * Constructor: mxEventSource * * Constructs a new dictionary which allows object to be used as keys. */ function mxDictionary() { this.clear(); }; /** * Function: map * * Stores the (key, value) pairs in this dictionary. */ mxDictionary.prototype.map = null; /** * Function: clear * * Clears the dictionary. */ mxDictionary.prototype.clear = function() { this.map = {}; }; /** * Function: get * * Returns the value for the given key. */ mxDictionary.prototype.get = function(key) { var id = mxObjectIdentity.get(key); return this.map[id]; }; /** * Function: put * * Stores the value under the given key and returns the previous * value for that key. */ mxDictionary.prototype.put = function(key, value) { var id = mxObjectIdentity.get(key); var previous = this.map[id]; this.map[id] = value; return previous; }; /** * Function: remove * * Removes the value for the given key and returns the value that * has been removed. */ mxDictionary.prototype.remove = function(key) { var id = mxObjectIdentity.get(key); var previous = this.map[id]; delete this.map[id]; return previous; }; /** * Function: getKeys * * Returns all keys as an array. */ mxDictionary.prototype.getKeys = function() { var result = []; for (var key in this.map) { result.push(key); } return result; }; /** * Function: getValues * * Returns all values as an array. */ mxDictionary.prototype.getValues = function() { var result = []; for (var key in this.map) { result.push(this.map[key]); } return result; }; /** * Function: visit * * Visits all entries in the dictionary using the given function with the * following signature: function(key, value) where key is a string and * value is an object. * * Parameters: * * visitor - A function that takes the key and value as arguments. */ mxDictionary.prototype.visit = function(visitor) { for (var key in this.map) { visitor(key, this.map[key]); } }; /** * $Id: mxResources.js,v 1.32 2012-10-26 13:36:50 gaudenz Exp $ * Copyright (c) 2006-2010, JGraph Ltd */ var mxResources = { /** * Class: mxResources * * Implements internationalization. You can provide any number of * resource files on the server using the following format for the * filename: name[-en].properties. The en stands for any lowercase * 2-character language shortcut (eg. de for german, fr for french). * * If the optional language extension is omitted, then the file is used as a * default resource which is loaded in all cases. If a properties file for a * specific language exists, then it is used to override the settings in the * default resource. All entries in the file are of the form key=value. The * values may then be accessed in code via . Lines without * equal signs in the properties files are ignored. * * Resource files may either be added programmatically using * or via a resource tag in the UI section of the * editor configuration file, eg: * * (code) * * * * (end) * * The above element will load examples/resources/mxWorkflow.properties as well * as the language specific file for the current language, if it exists. * * Values may contain placeholders of the form {1}...{n} where each placeholder * is replaced with the value of the corresponding array element in the params * argument passed to . The placeholder {1} maps to the first * element in the array (at index 0). * * See for more information on specifying the default * language or disabling all loading of resources. * * Lines that start with a # sign will be ignored. * * Special characters * * To use unicode characters, use the standard notation (eg. \u8fd1) or %u as a * prefix (eg. %u20AC will display a Euro sign). For normal hex encoded strings, * use % as a prefix, eg. %F6 will display a � (ö). * * See to disable this. If you disable this, make sure that * your files are UTF-8 encoded. * * Variable: resources * * Associative array that maps from keys to values. */ resources: [], /** * Variable: extension * * Specifies the extension used for language files. Default is '.properties'. */ extension: '.properties', /** * Variable: resourcesEncoded * * Specifies whether or not values in resource files are encoded with \u or * percentage. Default is true. */ resourcesEncoded: true, /** * Variable: loadDefaultBundle * * Specifies if the default file for a given basename should be loaded. * Default is true. */ loadDefaultBundle: true, /** * Variable: loadDefaultBundle * * Specifies if the specific language file file for a given basename should * be loaded. Default is true. */ loadSpecialBundle: true, /** * Function: isBundleSupported * * Hook for subclassers to disable support for a given language. This * implementation always returns true. * * Parameters: * * basename - The basename for which the file should be loaded. * lan - The current language. */ isLanguageSupported: function(lan) { if (mxClient.languages != null) { return mxUtils.indexOf(mxClient.languages, lan) >= 0; } return true; }, /** * Function: getDefaultBundle * * Hook for subclassers to return the URL for the special bundle. This * implementation returns basename + or null if * is false. * * Parameters: * * basename - The basename for which the file should be loaded. * lan - The current language. */ getDefaultBundle: function(basename, lan) { if (mxResources.loadDefaultBundle || !mxResources.isLanguageSupported(lan)) { return basename + mxResources.extension; } else { return null; } }, /** * Function: getSpecialBundle * * Hook for subclassers to return the URL for the special bundle. This * implementation returns basename + '_' + lan + or null if * is false or lan equals . * * If is not null and contains * a dash, then this method checks if returns true * for the full language (including the dash). If that returns false the * first part of the language (up to the dash) will be tried as an extension. * * If is null then the first part of the language is * used to maintain backwards compatibility. * * Parameters: * * basename - The basename for which the file should be loaded. * lan - The language for which the file should be loaded. */ getSpecialBundle: function(basename, lan) { if (mxClient.languages == null || !this.isLanguageSupported(lan)) { var dash = lan.indexOf('-'); if (dash > 0) { lan = lan.substring(0, dash); } } if (mxResources.loadSpecialBundle && mxResources.isLanguageSupported(lan) && lan != mxClient.defaultLanguage) { return basename + '_' + lan + mxResources.extension; } else { return null; } }, /** * Function: add * * Adds the default and current language properties * file for the specified basename. Existing keys * are overridden as new files are added. * * Example: * * At application startup, additional resources may be * added using the following code: * * (code) * mxResources.add('resources/editor'); * (end) */ add: function(basename, lan) { lan = (lan != null) ? lan : mxClient.language.toLowerCase(); if (lan != mxConstants.NONE) { // Loads the common language file (no extension) var defaultBundle = mxResources.getDefaultBundle(basename, lan); if (defaultBundle != null) { try { var req = mxUtils.load(defaultBundle); if (req.isReady()) { mxResources.parse(req.getText()); } } catch (e) { // ignore } } // Overlays the language specific file (_lan-extension) var specialBundle = mxResources.getSpecialBundle(basename, lan); if (specialBundle != null) { try { var req = mxUtils.load(specialBundle); if (req.isReady()) { mxResources.parse(req.getText()); } } catch (e) { // ignore } } } }, /** * Function: parse * * Parses the key, value pairs in the specified * text and stores them as local resources. */ parse: function(text) { if (text != null) { var lines = text.split('\n'); for (var i = 0; i < lines.length; i++) { if (lines[i].charAt(0) != '#') { var index = lines[i].indexOf('='); if (index > 0) { var key = lines[i].substring(0, index); var idx = lines[i].length; if (lines[i].charCodeAt(idx - 1) == 13) { idx--; } var value = lines[i].substring(index + 1, idx); if (this.resourcesEncoded) { value = value.replace(/\\(?=u[a-fA-F\d]{4})/g,"%"); mxResources.resources[key] = unescape(value); } else { mxResources.resources[key] = value; } } } } } }, /** * Function: get * * Returns the value for the specified resource key. * * Example: * To read the value for 'welomeMessage', use the following: * (code) * var result = mxResources.get('welcomeMessage') || ''; * (end) * * This would require an entry of the following form in * one of the English language resource files: * (code) * welcomeMessage=Welcome to mxGraph! * (end) * * The part behind the || is the string value to be used if the given * resource is not available. * * Parameters: * * key - String that represents the key of the resource to be returned. * params - Array of the values for the placeholders of the form {1}...{n} * to be replaced with in the resulting string. * defaultValue - Optional string that specifies the default return value. */ get: function(key, params, defaultValue) { var value = mxResources.resources[key]; // Applies the default value if no resource was found if (value == null) { value = defaultValue; } // Replaces the placeholders with the values in the array if (value != null && params != null) { var result = []; var index = null; for (var i = 0; i < value.length; i++) { var c = value.charAt(i); if (c == '{') { index = ''; } else if (index != null && c == '}') { index = parseInt(index)-1; if (index >= 0 && index < params.length) { result.push(params[index]); } index = null; } else if (index != null) { index += c; } else { result.push(c); } } value = result.join(''); } return value; } }; /** * $Id: mxPoint.js,v 1.12 2010-01-02 09:45:14 gaudenz Exp $ * Copyright (c) 2006-2010, JGraph Ltd */ /** * Class: mxPoint * * Implements a 2-dimensional vector with double precision coordinates. * * Constructor: mxPoint * * Constructs a new point for the optional x and y coordinates. If no * coordinates are given, then the default values for and are used. */ function mxPoint(x, y) { this.x = (x != null) ? x : 0; this.y = (y != null) ? y : 0; }; /** * Variable: x * * Holds the x-coordinate of the point. Default is 0. */ mxPoint.prototype.x = null; /** * Variable: y * * Holds the y-coordinate of the point. Default is 0. */ mxPoint.prototype.y = null; /** * Function: equals * * Returns true if the given object equals this rectangle. */ mxPoint.prototype.equals = function(obj) { return obj.x == this.x && obj.y == this.y; }; /** * Function: clone * * Returns a clone of this . */ mxPoint.prototype.clone = function() { // Handles subclasses as well return mxUtils.clone(this); }; /** * $Id: mxRectangle.js,v 1.17 2010-12-08 12:46:03 gaudenz Exp $ * Copyright (c) 2006-2010, JGraph Ltd */ /** * Class: mxRectangle * * Extends to implement a 2-dimensional rectangle with double * precision coordinates. * * Constructor: mxRectangle * * Constructs a new rectangle for the optional parameters. If no parameters * are given then the respective default values are used. */ function mxRectangle(x, y, width, height) { mxPoint.call(this, x, y); this.width = (width != null) ? width : 0; this.height = (height != null) ? height : 0; }; /** * Extends mxPoint. */ mxRectangle.prototype = new mxPoint(); mxRectangle.prototype.constructor = mxRectangle; /** * Variable: width * * Holds the width of the rectangle. Default is 0. */ mxRectangle.prototype.width = null; /** * Variable: height * * Holds the height of the rectangle. Default is 0. */ mxRectangle.prototype.height = null; /** * Function: setRect * * Sets this rectangle to the specified values */ mxRectangle.prototype.setRect = function(x, y, w, h) { this.x = x; this.y = y; this.width = w; this.height = h; }; /** * Function: getCenterX * * Returns the x-coordinate of the center point. */ mxRectangle.prototype.getCenterX = function () { return this.x + this.width/2; }; /** * Function: getCenterY * * Returns the y-coordinate of the center point. */ mxRectangle.prototype.getCenterY = function () { return this.y + this.height/2; }; /** * Function: add * * Adds the given rectangle to this rectangle. */ mxRectangle.prototype.add = function(rect) { if (rect != null) { var minX = Math.min(this.x, rect.x); var minY = Math.min(this.y, rect.y); var maxX = Math.max(this.x + this.width, rect.x + rect.width); var maxY = Math.max(this.y + this.height, rect.y + rect.height); this.x = minX; this.y = minY; this.width = maxX - minX; this.height = maxY - minY; } }; /** * Function: grow * * Grows the rectangle by the given amount, that is, this method subtracts * the given amount from the x- and y-coordinates and adds twice the amount * to the width and height. */ mxRectangle.prototype.grow = function(amount) { this.x -= amount; this.y -= amount; this.width += 2 * amount; this.height += 2 * amount; }; /** * Function: getPoint * * Returns the top, left corner as a new . */ mxRectangle.prototype.getPoint = function() { return new mxPoint(this.x, this.y); }; /** * Function: equals * * Returns true if the given object equals this rectangle. */ mxRectangle.prototype.equals = function(obj) { return obj.x == this.x && obj.y == this.y && obj.width == this.width && obj.height == this.height; }; /** * $Id: mxEffects.js,v 1.6 2012-01-04 10:01:16 gaudenz Exp $ * Copyright (c) 2006-2010, JGraph Ltd */ var mxEffects = { /** * Class: mxEffects * * Provides animation effects. */ /** * Function: animateChanges * * Asynchronous animated move operation. See also: . * * Example: * * (code) * graph.model.addListener(mxEvent.CHANGE, function(sender, evt) * { * var changes = evt.getProperty('edit').changes; * * if (changes.length < 10) * { * mxEffects.animateChanges(graph, changes); * } * }); * (end) * * Parameters: * * graph - that received the changes. * changes - Array of changes to be animated. * done - Optional function argument that is invoked after the * last step of the animation. */ animateChanges: function(graph, changes, done) { var maxStep = 10; var step = 0; var animate = function() { var isRequired = false; for (var i = 0; i < changes.length; i++) { var change = changes[i]; if (change instanceof mxGeometryChange || change instanceof mxTerminalChange || change instanceof mxValueChange || change instanceof mxChildChange || change instanceof mxStyleChange) { var state = graph.getView().getState(change.cell || change.child, false); if (state != null) { isRequired = true; if (change.constructor != mxGeometryChange || graph.model.isEdge(change.cell)) { mxUtils.setOpacity(state.shape.node, 100 * step / maxStep); } else { var scale = graph.getView().scale; var dx = (change.geometry.x - change.previous.x) * scale; var dy = (change.geometry.y - change.previous.y) * scale; var sx = (change.geometry.width - change.previous.width) * scale; var sy = (change.geometry.height - change.previous.height) * scale; if (step == 0) { state.x -= dx; state.y -= dy; state.width -= sx; state.height -= sy; } else { state.x += dx / maxStep; state.y += dy / maxStep; state.width += sx / maxStep; state.height += sy / maxStep; } graph.cellRenderer.redraw(state); // Fades all connected edges and children mxEffects.cascadeOpacity(graph, change.cell, 100 * step / maxStep); } } } } // Workaround to force a repaint in AppleWebKit mxUtils.repaintGraph(graph, new mxPoint(1, 1)); if (step < maxStep && isRequired) { step++; window.setTimeout(animate, delay); } else if (done != null) { done(); } }; var delay = 30; animate(); }, /** * Function: cascadeOpacity * * Sets the opacity on the given cell and its descendants. * * Parameters: * * graph - that contains the cells. * cell - to set the opacity for. * opacity - New value for the opacity in %. */ cascadeOpacity: function(graph, cell, opacity) { // Fades all children var childCount = graph.model.getChildCount(cell); for (var i=0; i 0) { window.setTimeout(f, delay); } else { node.style.visibility = 'hidden'; if (remove && node.parentNode) { node.parentNode.removeChild(node); } } }; window.setTimeout(f, delay); } else { node.style.visibility = 'hidden'; if (remove && node.parentNode) { node.parentNode.removeChild(node); } } } }; /** * $Id: mxUtils.js,v 1.297 2012-12-07 19:47:29 gaudenz Exp $ * Copyright (c) 2006-2010, JGraph Ltd */ var mxUtils = { /** * Class: mxUtils * * A singleton class that provides cross-browser helper methods. * This is a global functionality. To access the functions in this * class, use the global classname appended by the functionname. * You may have to load chrome://global/content/contentAreaUtils.js * to disable certain security restrictions in Mozilla for the , * , and function. * * For example, the following code displays an error message: * * (code) * mxUtils.error('Browser is not supported!', 200, false); * (end) * * Variable: errorResource * * Specifies the resource key for the title of the error window. If the * resource for this key does not exist then the value is used as * the title. Default is 'error'. */ errorResource: (mxClient.language != 'none') ? 'error' : '', /** * Variable: closeResource * * Specifies the resource key for the label of the close button. If the * resource for this key does not exist then the value is used as * the label. Default is 'close'. */ closeResource: (mxClient.language != 'none') ? 'close' : '', /** * Variable: errorImage * * Defines the image used for error dialogs. */ errorImage: mxClient.imageBasePath + '/error.gif', /** * Function: removeCursors * * Removes the cursors from the style of the given DOM node and its * descendants. * * Parameters: * * element - DOM node to remove the cursor style from. */ removeCursors: function(element) { if (element.style != null) { element.style.cursor = ''; } var children = element.childNodes; if (children != null) { var childCount = children.length; for (var i = 0; i < childCount; i += 1) { mxUtils.removeCursors(children[i]); } } }, /** * Function: repaintGraph * * Normally not required, this contains the code to workaround a repaint * issue and force a repaint of the graph container in AppleWebKit. * * Parameters: * * graph - to be repainted. * pt - where the dummy element should be placed. */ repaintGraph: function(graph, pt) { if (mxClient.IS_GC || mxClient.IS_SF || mxClient.IS_OP) { var c = graph.container; if (c != null && pt != null && (c.scrollLeft > 0 || c.scrollTop > 0)) { var dummy = document.createElement('div'); dummy.style.position = 'absolute'; dummy.style.left = pt.x + 'px'; dummy.style.top = pt.y + 'px'; dummy.style.width = '1px'; dummy.style.height = '1px'; c.appendChild(dummy); c.removeChild(dummy); } } }, /** * Function: getCurrentStyle * * Returns the current style of the specified element. * * Parameters: * * element - DOM node whose current style should be returned. */ getCurrentStyle: function() { if (mxClient.IS_IE) { return function(element) { return (element != null) ? element.currentStyle : null; }; } else { return function(element) { return (element != null) ? window.getComputedStyle(element, '') : null; }; } }(), /** * Function: hasScrollbars * * Returns true if the overflow CSS property of the given node is either * scroll or auto. * * Parameters: * * node - DOM node whose style should be checked for scrollbars. */ hasScrollbars: function(node) { var style = mxUtils.getCurrentStyle(node); return style != null && (style.overflow == 'scroll' || style.overflow == 'auto'); }, /** * Function: bind * * Returns a wrapper function that locks the execution scope of the given * function to the specified scope. Inside funct, the "this" keyword * becomes a reference to that scope. */ bind: function(scope, funct) { return function() { return funct.apply(scope, arguments); }; }, /** * Function: eval * * Evaluates the given expression using eval and returns the JavaScript * object that represents the expression result. Supports evaluation of * expressions that define functions and returns the function object for * these expressions. * * Parameters: * * expr - A string that represents a JavaScript expression. */ eval: function(expr) { var result = null; if (expr.indexOf('function') >= 0) { try { eval('var _mxJavaScriptExpression='+expr); result = _mxJavaScriptExpression; // TODO: Use delete here? _mxJavaScriptExpression = null; } catch (e) { mxLog.warn(e.message + ' while evaluating ' + expr); } } else { try { result = eval(expr); } catch (e) { mxLog.warn(e.message + ' while evaluating ' + expr); } } return result; }, /** * Function: findNode * * Returns the first node where attr equals value. * This implementation does not use XPath. */ findNode: function(node, attr, value) { var tmp = node.getAttribute(attr); if (tmp != null && tmp == value) { return node; } node = node.firstChild; while (node != null) { var result = mxUtils.findNode(node, attr, value); if (result != null) { return result; } node = node.nextSibling; } return null; }, /** * Function: findNodeByAttribute * * Returns the first node where the given attribute matches the given value. * * Parameters: * * node - Root node where the search should start. * attr - Name of the attribute to be checked. * value - Value of the attribute to match. */ findNodeByAttribute: function() { // Workaround for missing XPath support in IE9 if (document.documentMode >= 9) { return function(node, attr, value) { var result = null; if (node != null) { if (node.nodeType == mxConstants.NODETYPE_ELEMENT && node.getAttribute(attr) == value) { result = node; } else { var child = node.firstChild; while (child != null && result == null) { result = mxUtils.findNodeByAttribute(child, attr, value); child = child.nextSibling; } } } return result; }; } else if (mxClient.IS_IE) { return function(node, attr, value) { if (node == null) { return null; } else { var expr = '//*[@' + attr + '=\'' + value + '\']'; return node.ownerDocument.selectSingleNode(expr); } }; } else { return function(node, attr, value) { if (node == null) { return null; } else { var result = node.ownerDocument.evaluate( '//*[@' + attr + '=\'' + value + '\']', node.ownerDocument, null, XPathResult.ANY_TYPE, null); return result.iterateNext(); } }; } }(), /** * Function: getFunctionName * * Returns the name for the given function. * * Parameters: * * f - JavaScript object that represents a function. */ getFunctionName: function(f) { var str = null; if (f != null) { if (f.name != null) { str = f.name; } else { var tmp = f.toString(); var idx1 = 9; while (tmp.charAt(idx1) == ' ') { idx1++; } var idx2 = tmp.indexOf('(', idx1); str = tmp.substring(idx1, idx2); } } return str; }, /** * Function: indexOf * * Returns the index of obj in array or -1 if the array does not contains * the given object. * * Parameters: * * array - Array to check for the given obj. * obj - Object to find in the given array. */ indexOf: function(array, obj) { if (array != null && obj != null) { for (var i = 0; i < array.length; i++) { if (array[i] == obj) { return i; } } } return -1; }, /** * Function: remove * * Removes all occurrences of the given object in the given array or * object. If there are multiple occurrences of the object, be they * associative or as an array entry, all occurrences are removed from * the array or deleted from the object. By removing the object from * the array, all elements following the removed element are shifted * by one step towards the beginning of the array. * * The length of arrays is not modified inside this function. * * Parameters: * * obj - Object to find in the given array. * array - Array to check for the given obj. */ remove: function(obj, array) { var result = null; if (typeof(array) == 'object') { var index = mxUtils.indexOf(array, obj); while (index >= 0) { array.splice(index, 1); result = obj; index = mxUtils.indexOf(array, obj); } } for (var key in array) { if (array[key] == obj) { delete array[key]; result = obj; } } return result; }, /** * Function: isNode * * Returns true if the given value is an XML node with the node name * and if the optional attribute has the specified value. * * This implementation assumes that the given value is a DOM node if the * nodeType property is numeric, that is, if isNaN returns false for * value.nodeType. * * Parameters: * * value - Object that should be examined as a node. * nodeName - String that specifies the node name. * attributeName - Optional attribute name to check. * attributeValue - Optional attribute value to check. */ isNode: function(value, nodeName, attributeName, attributeValue) { if (value != null && !isNaN(value.nodeType) && (nodeName == null || value.nodeName.toLowerCase() == nodeName.toLowerCase())) { return attributeName == null || value.getAttribute(attributeName) == attributeValue; } return false; }, /** * Function: getChildNodes * * Returns an array of child nodes that are of the given node type. * * Parameters: * * node - Parent DOM node to return the children from. * nodeType - Optional node type to return. Default is * . */ getChildNodes: function(node, nodeType) { nodeType = nodeType || mxConstants.NODETYPE_ELEMENT; var children = []; var tmp = node.firstChild; while (tmp != null) { if (tmp.nodeType == nodeType) { children.push(tmp); } tmp = tmp.nextSibling; } return children; }, /** * Function: createXmlDocument * * Returns a new, empty XML document. */ createXmlDocument: function() { var doc = null; if (document.implementation && document.implementation.createDocument) { doc = document.implementation.createDocument('', '', null); } else if (window.ActiveXObject) { doc = new ActiveXObject('Microsoft.XMLDOM'); } return doc; }, /** * Function: parseXml * * Parses the specified XML string into a new XML document and returns the * new document. * * Example: * * (code) * var doc = mxUtils.parseXml( * ''+ * ''+ * ''+ * ''+ * ''); * (end) * * Parameters: * * xml - String that contains the XML data. */ parseXml: function() { if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9)) { return function(xml) { var result = mxUtils.createXmlDocument(); result.async = 'false'; result.loadXML(xml); return result; }; } else { return function(xml) { var parser = new DOMParser(); return parser.parseFromString(xml, 'text/xml'); }; } }(), /** * Function: clearSelection * * Clears the current selection in the page. */ clearSelection: function() { if (document.selection) { return function() { document.selection.empty(); }; } else if (window.getSelection) { return function() { window.getSelection().removeAllRanges(); }; } }(), /** * Function: getPrettyXML * * Returns a pretty printed string that represents the XML tree for the * given node. This method should only be used to print XML for reading, * use instead to obtain a string for processing. * * Parameters: * * node - DOM node to return the XML for. * tab - Optional string that specifies the indentation for one level. * Default is two spaces. * indent - Optional string that represents the current indentation. * Default is an empty string. */ getPrettyXml: function(node, tab, indent) { var result = []; if (node != null) { tab = tab || ' '; indent = indent || ''; if (node.nodeType == mxConstants.NODETYPE_TEXT) { result.push(node.nodeValue); } else { result.push(indent + '<'+node.nodeName); // Creates the string with the node attributes // and converts all HTML entities in the values var attrs = node.attributes; if (attrs != null) { for (var i = 0; i < attrs.length; i++) { var val = mxUtils.htmlEntities(attrs[i].nodeValue); result.push(' ' + attrs[i].nodeName + '="' + val + '"'); } } // Recursively creates the XML string for each // child nodes and appends it here with an // indentation var tmp = node.firstChild; if (tmp != null) { result.push('>\n'); while (tmp != null) { result.push(mxUtils.getPrettyXml( tmp, tab, indent + tab)); tmp = tmp.nextSibling; } result.push(indent + '\n'); } else { result.push('/>\n'); } } } return result.join(''); }, /** * Function: removeWhitespace * * Removes the sibling text nodes for the given node that only consists * of tabs, newlines and spaces. * * Parameters: * * node - DOM node whose siblings should be removed. * before - Optional boolean that specifies the direction of the traversal. */ removeWhitespace: function(node, before) { var tmp = (before) ? node.previousSibling : node.nextSibling; while (tmp != null && tmp.nodeType == mxConstants.NODETYPE_TEXT) { var next = (before) ? tmp.previousSibling : tmp.nextSibling; var text = mxUtils.getTextContent(tmp); if (mxUtils.trim(text).length == 0) { tmp.parentNode.removeChild(tmp); } tmp = next; } }, /** * Function: htmlEntities * * Replaces characters (less than, greater than, newlines and quotes) with * their HTML entities in the given string and returns the result. * * Parameters: * * s - String that contains the characters to be converted. * newline - If newlines should be replaced. Default is true. */ htmlEntities: function(s, newline) { s = s || ''; s = s.replace(/&/g,'&'); // 38 26 s = s.replace(/"/g,'"'); // 34 22 s = s.replace(/\'/g,'''); // 39 27 s = s.replace(//g,'>'); // 62 3E if (newline == null || newline) { s = s.replace(/\n/g, ' '); } return s; }, /** * Function: isVml * * Returns true if the given node is in the VML namespace. * * Parameters: * * node - DOM node whose tag urn should be checked. */ isVml: function(node) { return node != null && node.tagUrn == 'urn:schemas-microsoft-com:vml'; }, /** * Function: getXml * * Returns the XML content of the specified node. For Internet Explorer, * all \r\n\t[\t]* are removed from the XML string and the remaining \r\n * are replaced by \n. All \n are then replaced with linefeed, or if * no linefeed is defined. * * Parameters: * * node - DOM node to return the XML for. * linefeed - Optional string that linefeeds are converted into. Default is * */ getXml: function(node, linefeed) { var xml = ''; if (node != null) { xml = node.xml; if (xml == null) { if (node.innerHTML) { xml = node.innerHTML; } else { var xmlSerializer = new XMLSerializer(); xml = xmlSerializer.serializeToString(node); } } else { xml = xml.replace(/\r\n\t[\t]*/g, ''). replace(/>\r\n/g, '>'). replace(/\r\n/g, '\n'); } } // Replaces linefeeds with HTML Entities. linefeed = linefeed || ' '; xml = xml.replace(/\n/g, linefeed); return xml; }, /** * Function: getTextContent * * Returns the text content of the specified node. * * Parameters: * * node - DOM node to return the text content for. */ getTextContent: function(node) { var result = ''; if (node != null) { if (node.firstChild != null) { node = node.firstChild; } result = node.nodeValue || ''; } return result; }, /** * Function: getInnerHtml * * Returns the inner HTML for the given node as a string or an empty string * if no node was specified. The inner HTML is the text representing all * children of the node, but not the node itself. * * Parameters: * * node - DOM node to return the inner HTML for. */ getInnerHtml: function() { if (mxClient.IS_IE) { return function(node) { if (node != null) { return node.innerHTML; } return ''; }; } else { return function(node) { if (node != null) { var serializer = new XMLSerializer(); return serializer.serializeToString(node); } return ''; }; } }(), /** * Function: getOuterHtml * * Returns the outer HTML for the given node as a string or an empty * string if no node was specified. The outer HTML is the text representing * all children of the node including the node itself. * * Parameters: * * node - DOM node to return the outer HTML for. */ getOuterHtml: function() { if (mxClient.IS_IE) { return function(node) { if (node != null) { if (node.outerHTML != null) { return node.outerHTML; } else { var tmp = []; tmp.push('<'+node.nodeName); var attrs = node.attributes; if (attrs != null) { for (var i = 0; i < attrs.length; i++) { var value = attrs[i].nodeValue; if (value != null && value.length > 0) { tmp.push(' '); tmp.push(attrs[i].nodeName); tmp.push('="'); tmp.push(value); tmp.push('"'); } } } if (node.innerHTML.length == 0) { tmp.push('/>'); } else { tmp.push('>'); tmp.push(node.innerHTML); tmp.push(''); } return tmp.join(''); } } return ''; }; } else { return function(node) { if (node != null) { var serializer = new XMLSerializer(); return serializer.serializeToString(node); } return ''; }; } }(), /** * Function: write * * Creates a text node for the given string and appends it to the given * parent. Returns the text node. * * Parameters: * * parent - DOM node to append the text node to. * text - String representing the text to be added. */ write: function(parent, text) { var doc = parent.ownerDocument; var node = doc.createTextNode(text); if (parent != null) { parent.appendChild(node); } return node; }, /** * Function: writeln * * Creates a text node for the given string and appends it to the given * parent with an additional linefeed. Returns the text node. * * Parameters: * * parent - DOM node to append the text node to. * text - String representing the text to be added. */ writeln: function(parent, text) { var doc = parent.ownerDocument; var node = doc.createTextNode(text); if (parent != null) { parent.appendChild(node); parent.appendChild(document.createElement('br')); } return node; }, /** * Function: br * * Appends a linebreak to the given parent and returns the linebreak. * * Parameters: * * parent - DOM node to append the linebreak to. */ br: function(parent, count) { count = count || 1; var br = null; for (var i = 0; i < count; i++) { if (parent != null) { br = parent.ownerDocument.createElement('br'); parent.appendChild(br); } } return br; }, /** * Function: button * * Returns a new button with the given level and function as an onclick * event handler. * * (code) * document.body.appendChild(mxUtils.button('Test', function(evt) * { * alert('Hello, World!'); * })); * (end) * * Parameters: * * label - String that represents the label of the button. * funct - Function to be called if the button is pressed. * doc - Optional document to be used for creating the button. Default is the * current document. */ button: function(label, funct, doc) { doc = (doc != null) ? doc : document; var button = doc.createElement('button'); mxUtils.write(button, label); mxEvent.addListener(button, 'click', function(evt) { funct(evt); }); return button; }, /** * Function: para * * Appends a new paragraph with the given text to the specified parent and * returns the paragraph. * * Parameters: * * parent - DOM node to append the text node to. * text - String representing the text for the new paragraph. */ para: function(parent, text) { var p = document.createElement('p'); mxUtils.write(p, text); if (parent != null) { parent.appendChild(p); } return p; }, /** * Function: linkAction * * Adds a hyperlink to the specified parent that invokes action on the * specified editor. * * Parameters: * * parent - DOM node to contain the new link. * text - String that is used as the link label. * editor - that will execute the action. * action - String that defines the name of the action to be executed. * pad - Optional left-padding for the link. Default is 0. */ linkAction: function(parent, text, editor, action, pad) { return mxUtils.link(parent, text, function() { editor.execute(action); }, pad); }, /** * Function: linkInvoke * * Adds a hyperlink to the specified parent that invokes the specified * function on the editor passing along the specified argument. The * function name is the name of a function of the editor instance, * not an action name. * * Parameters: * * parent - DOM node to contain the new link. * text - String that is used as the link label. * editor - instance to execute the function on. * functName - String that represents the name of the function. * arg - Object that represents the argument to the function. * pad - Optional left-padding for the link. Default is 0. */ linkInvoke: function(parent, text, editor, functName, arg, pad) { return mxUtils.link(parent, text, function() { editor[functName](arg); }, pad); }, /** * Function: link * * Adds a hyperlink to the specified parent and invokes the given function * when the link is clicked. * * Parameters: * * parent - DOM node to contain the new link. * text - String that is used as the link label. * funct - Function to execute when the link is clicked. * pad - Optional left-padding for the link. Default is 0. */ link: function(parent, text, funct, pad) { var a = document.createElement('span'); a.style.color = 'blue'; a.style.textDecoration = 'underline'; a.style.cursor = 'pointer'; if (pad != null) { a.style.paddingLeft = pad+'px'; } mxEvent.addListener(a, 'click', funct); mxUtils.write(a, text); if (parent != null) { parent.appendChild(a); } return a; }, /** * Function: fit * * Makes sure the given node is inside the visible area of the window. This * is done by setting the left and top in the style. */ fit: function(node) { var left = parseInt(node.offsetLeft); var width = parseInt(node.offsetWidth); var b = document.body; var d = document.documentElement; var right = (b.scrollLeft || d.scrollLeft) + (b.clientWidth || d.clientWidth); if (left + width > right) { node.style.left = Math.max((b.scrollLeft || d.scrollLeft), right - width)+'px'; } var top = parseInt(node.offsetTop); var height = parseInt(node.offsetHeight); var bottom = (b.scrollTop || d.scrollTop) + Math.max(b.clientHeight || 0, d.clientHeight); if (top + height > bottom) { node.style.top = Math.max((b.scrollTop || d.scrollTop), bottom - height)+'px'; } }, /** * Function: open * * Opens the specified file from the local filesystem and returns the * contents of the file as a string. This implementation requires an * ActiveX object in IE and special privileges in Firefox. Relative * filenames are only supported in IE and will go onto the users' * Desktop. You may have to load * chrome://global/content/contentAreaUtils.js to disable certain * security restrictions in Mozilla for this to work. * * See known-issues before using this function. * * Example: * (code) * var data = mxUtils.open('C:\\temp\\test.txt'); * mxUtils.alert('Data: '+data); * (end) * * Parameters: * * filename - String representing the local file name. */ open: function(filename) { // Requests required privileges in Firefox if (mxClient.IS_NS) { try { netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); } catch (e) { mxUtils.alert('Permission to read file denied.'); return ''; } var file = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile); file.initWithPath(filename); if (!file.exists()) { mxUtils.alert('File not found.'); return ''; } var is = Components.classes['@mozilla.org/network/file-input-stream;1'].createInstance(Components.interfaces.nsIFileInputStream); is.init(file,0x01, 00004, null); var sis = Components.classes['@mozilla.org/scriptableinputstream;1'].createInstance(Components.interfaces.nsIScriptableInputStream); sis.init(is); var output = sis.read(sis.available()); return output; } else { var activeXObject = new ActiveXObject('Scripting.FileSystemObject'); var newStream = activeXObject.OpenTextFile(filename, 1); var text = newStream.readAll(); newStream.close(); return text; } }, /** * Function: save * * Saves the specified content in the given file on the local file system. * This implementation requires an ActiveX object in IE and special * privileges in Firefox. Relative filenames are only supported in IE and * will be loaded from the users' Desktop. You may have to load * chrome://global/content/contentAreaUtils.js to disable certain * security restrictions in Mozilla for this to work. * * See known-issues before using this function. * * Example: * * (code) * var data = 'Hello, World!'; * mxUtils.save('C:\\test.txt', data); * (end) * * Parameters: * * filename - String representing the local file name. */ save: function(filename, content) { if (mxClient.IS_NS) { try { netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); } catch (e) { mxUtils.alert('Permission to write file denied.'); return; } var file = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile); file.initWithPath(filename); if (!file.exists()) { file.create(0x00, 0644); } var outputStream = Components.classes['@mozilla.org/network/file-output-stream;1'].createInstance(Components.interfaces.nsIFileOutputStream); outputStream.init(file, 0x20 | 0x02,00004, null); outputStream.write(content, content.length); outputStream.flush(); outputStream.close(); } else { var fso = new ActiveXObject('Scripting.FileSystemObject'); var file = fso.CreateTextFile(filename, true); file.Write(content); file.Close(); } }, /** * Function: saveAs * * Saves the specified content by displaying a dialog to save the content * as a file on the local filesystem. This implementation does not use an * ActiveX object in IE, however, it does require special privileges in * Firefox. You may have to load * chrome://global/content/contentAreaUtils.js to disable certain * security restrictions in Mozilla for this to work. * * See known-issues before using this function. It is not recommended using * this function in production environment as access to the filesystem * cannot be guaranteed in Firefox. The following code is used in * Firefox to try and enable saving to the filesystem. * * (code) * netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); * (end) * * Example: * * (code) * mxUtils.saveAs('Hello, World!'); * (end) * * Parameters: * * content - String representing the file's content. */ saveAs: function(content) { var iframe = document.createElement('iframe'); iframe.setAttribute('src', ''); iframe.style.visibility = 'hidden'; document.body.appendChild(iframe); try { if (mxClient.IS_NS) { var doc = iframe.contentDocument; doc.open(); doc.write(content); doc.close(); try { netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); // LATER: Remove existing HTML markup in file iframe.focus(); saveDocument(doc); } catch (e) { mxUtils.alert('Permission to save document denied.'); } } else { var doc = iframe.contentWindow.document; doc.write(content); doc.execCommand('SaveAs', false, document.location); } } finally { document.body.removeChild(iframe); } }, /** * Function: copy * * Copies the specified content to the local clipboard. This implementation * requires special privileges in Firefox. You may have to load * chrome://global/content/contentAreaUtils.js to disable certain * security restrictions in Mozilla for this to work. * * Parameters: * * content - String to be copied to the clipboard. */ copy: function(content) { if (window.clipboardData) { window.clipboardData.setData('Text', content); } else { netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); var clip = Components.classes['@mozilla.org/widget/clipboard;1'] .createInstance(Components.interfaces.nsIClipboard); if (!clip) { return; } var trans = Components.classes['@mozilla.org/widget/transferable;1'] .createInstance(Components.interfaces.nsITransferable); if (!trans) { return; } trans.addDataFlavor('text/unicode'); var str = Components.classes['@mozilla.org/supports-string;1'] .createInstance(Components.interfaces.nsISupportsString); var copytext=content; str.data=copytext; trans.setTransferData('text/unicode', str, copytext.length*2); var clipid=Components.interfaces.nsIClipboard; clip.setData(trans,null,clipid.kGlobalClipboard); } }, /** * Function: load * * Loads the specified URL *synchronously* and returns the . * Throws an exception if the file cannot be loaded. See for * an asynchronous implementation. * * Example: * * (code) * try * { * var req = mxUtils.load(filename); * var root = req.getDocumentElement(); * // Process XML DOM... * } * catch (ex) * { * mxUtils.alert('Cannot load '+filename+': '+ex); * } * (end) * * Parameters: * * url - URL to get the data from. */ load: function(url) { var req = new mxXmlRequest(url, null, 'GET', false); req.send(); return req; }, /** * Function: get * * Loads the specified URL *asynchronously* and invokes the given functions * depending on the request status. Returns the in use. Both * functions take the as the only parameter. See * for a synchronous implementation. * * Example: * * (code) * mxUtils.get(url, function(req) * { * var node = req.getDocumentElement(); * // Process XML DOM... * }); * (end) * * So for example, to load a diagram into an existing graph model, the * following code is used. * * (code) * mxUtils.get(url, function(req) * { * var node = req.getDocumentElement(); * var dec = new mxCodec(node.ownerDocument); * dec.decode(node, graph.getModel()); * }); * (end) * * Parameters: * * url - URL to get the data from. * onload - Optional function to execute for a successful response. * onerror - Optional function to execute on error. */ get: function(url, onload, onerror) { return new mxXmlRequest(url, null, 'GET').send(onload, onerror); }, /** * Function: post * * Posts the specified params to the given URL *asynchronously* and invokes * the given functions depending on the request status. Returns the * in use. Both functions take the as the * only parameter. Make sure to use encodeURIComponent for the parameter * values. * * Example: * * (code) * mxUtils.post(url, 'key=value', function(req) * { * mxUtils.alert('Ready: '+req.isReady()+' Status: '+req.getStatus()); * // Process req.getDocumentElement() using DOM API if OK... * }); * (end) * * Parameters: * * url - URL to get the data from. * params - Parameters for the post request. * onload - Optional function to execute for a successful response. * onerror - Optional function to execute on error. */ post: function(url, params, onload, onerror) { return new mxXmlRequest(url, params).send(onload, onerror); }, /** * Function: submit * * Submits the given parameters to the specified URL using * and returns the . * Make sure to use encodeURIComponent for the parameter * values. * * Parameters: * * url - URL to get the data from. * params - Parameters for the form. * doc - Document to create the form in. * target - Target to send the form result to. */ submit: function(url, params, doc, target) { return new mxXmlRequest(url, params).simulate(doc, target); }, /** * Function: loadInto * * Loads the specified URL *asynchronously* into the specified document, * invoking onload after the document has been loaded. This implementation * does not use , but the document.load method. * * Parameters: * * url - URL to get the data from. * doc - The document to load the URL into. * onload - Function to execute when the URL has been loaded. */ loadInto: function(url, doc, onload) { if (mxClient.IS_IE) { doc.onreadystatechange = function () { if (doc.readyState == 4) { onload(); } }; } else { doc.addEventListener('load', onload, false); } doc.load(url); }, /** * Function: getValue * * Returns the value for the given key in the given associative array or * the given default value if the value is null. * * Parameters: * * array - Associative array that contains the value for the key. * key - Key whose value should be returned. * defaultValue - Value to be returned if the value for the given * key is null. */ getValue: function(array, key, defaultValue) { var value = (array != null) ? array[key] : null; if (value == null) { value = defaultValue; } return value; }, /** * Function: getNumber * * Returns the numeric value for the given key in the given associative * array or the given default value (or 0) if the value is null. The value * is converted to a numeric value using the Number function. * * Parameters: * * array - Associative array that contains the value for the key. * key - Key whose value should be returned. * defaultValue - Value to be returned if the value for the given * key is null. Default is 0. */ getNumber: function(array, key, defaultValue) { var value = (array != null) ? array[key] : null; if (value == null) { value = defaultValue || 0; } return Number(value); }, /** * Function: getColor * * Returns the color value for the given key in the given associative * array or the given default value if the value is null. If the value * is then null is returned. * * Parameters: * * array - Associative array that contains the value for the key. * key - Key whose value should be returned. * defaultValue - Value to be returned if the value for the given * key is null. Default is null. */ getColor: function(array, key, defaultValue) { var value = (array != null) ? array[key] : null; if (value == null) { value = defaultValue; } else if (value == mxConstants.NONE) { value = null; } return value; }, /** * Function: clone * * Recursively clones the specified object ignoring all fieldnames in the * given array of transient fields. is always * ignored by this function. * * Parameters: * * obj - Object to be cloned. * transients - Optional array of strings representing the fieldname to be * ignored. * shallow - Optional boolean argument to specify if a shallow clone should * be created, that is, one where all object references are not cloned or, * in other words, one where only atomic (strings, numbers) values are * cloned. Default is false. */ clone: function(obj, transients, shallow) { shallow = (shallow != null) ? shallow : false; var clone = null; if (obj != null && typeof(obj.constructor) == 'function') { clone = new obj.constructor(); for (var i in obj) { if (i != mxObjectIdentity.FIELD_NAME && (transients == null || mxUtils.indexOf(transients, i) < 0)) { if (!shallow && typeof(obj[i]) == 'object') { clone[i] = mxUtils.clone(obj[i]); } else { clone[i] = obj[i]; } } } } return clone; }, /** * Function: equalPoints * * Compares all mxPoints in the given lists. * * Parameters: * * a - Array of to be compared. * b - Array of to be compared. */ equalPoints: function(a, b) { if ((a == null && b != null) || (a != null && b == null) || (a != null && b != null && a.length != b.length)) { return false; } else if (a != null && b != null) { for (var i = 0; i < a.length; i++) { if (a[i] == b[i] || (a[i] != null && !a[i].equals(b[i]))) { return false; } } } return true; }, /** * Function: equalEntries * * Compares all entries in the given dictionaries. * * Parameters: * * a - to be compared. * b - to be compared. */ equalEntries: function(a, b) { if ((a == null && b != null) || (a != null && b == null) || (a != null && b != null && a.length != b.length)) { return false; } else if (a != null && b != null) { for (var key in a) { if (a[key] != b[key]) { return false; } } } return true; }, /** * Function: extend * * Assigns a copy of the superclass prototype to the subclass prototype. * Note that this does not call the constructor of the superclass at this * point, the superclass constructor should be called explicitely in the * subclass constructor. Below is an example. * * (code) * MyGraph = function(container, model, renderHint, stylesheet) * { * mxGraph.call(this, container, model, renderHint, stylesheet); * } * * mxUtils.extend(MyGraph, mxGraph); * (end) * * Parameters: * * ctor - Constructor of the subclass. * superCtor - Constructor of the superclass. */ extend: function(ctor, superCtor) { var f = function() {}; f.prototype = superCtor.prototype; ctor.prototype = new f(); ctor.prototype.constructor = ctor; }, /** * Function: toString * * Returns a textual representation of the specified object. * * Parameters: * * obj - Object to return the string representation for. */ toString: function(obj) { var output = ''; for (var i in obj) { try { if (obj[i] == null) { output += i + ' = [null]\n'; } else if (typeof(obj[i]) == 'function') { output += i + ' => [Function]\n'; } else if (typeof(obj[i]) == 'object') { var ctor = mxUtils.getFunctionName(obj[i].constructor); output += i + ' => [' + ctor + ']\n'; } else { output += i + ' = ' + obj[i] + '\n'; } } catch (e) { output += i + '=' + e.message; } } return output; }, /** * Function: toRadians * * Converts the given degree to radians. */ toRadians: function(deg) { return Math.PI * deg / 180; }, /** * Function: arcToCurves * * Converts the given arc to a series of curves. */ arcToCurves: function(x0, y0, r1, r2, angle, largeArcFlag, sweepFlag, x, y) { x -= x0; y -= y0; if (r1 === 0 || r2 === 0) { return result; } var fS = sweepFlag; var psai = angle; r1 = Math.abs(r1); r2 = Math.abs(r2); var ctx = -x / 2; var cty = -y / 2; var cpsi = Math.cos(psai * Math.PI / 180); var spsi = Math.sin(psai * Math.PI / 180); var rxd = cpsi * ctx + spsi * cty; var ryd = -1 * spsi * ctx + cpsi * cty; var rxdd = rxd * rxd; var rydd = ryd * ryd; var r1x = r1 * r1; var r2y = r2 * r2; var lamda = rxdd / r1x + rydd / r2y; var sds; if (lamda > 1) { r1 = Math.sqrt(lamda) * r1; r2 = Math.sqrt(lamda) * r2; sds = 0; } else { var seif = 1; if (largeArcFlag === fS) { seif = -1; } sds = seif * Math.sqrt((r1x * r2y - r1x * rydd - r2y * rxdd) / (r1x * rydd + r2y * rxdd)); } var txd = sds * r1 * ryd / r2; var tyd = -1 * sds * r2 * rxd / r1; var tx = cpsi * txd - spsi * tyd + x / 2; var ty = spsi * txd + cpsi * tyd + y / 2; var rad = Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1) - Math.atan2(0, 1); var s1 = (rad >= 0) ? rad : 2 * Math.PI + rad; rad = Math.atan2((-ryd - tyd) / r2, (-rxd - txd) / r1) - Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1); var dr = (rad >= 0) ? rad : 2 * Math.PI + rad; if (fS == 0 && dr > 0) { dr -= 2 * Math.PI; } else if (fS != 0 && dr < 0) { dr += 2 * Math.PI; } var sse = dr * 2 / Math.PI; var seg = Math.ceil(sse < 0 ? -1 * sse : sse); var segr = dr / seg; var t = 8/3 * Math.sin(segr / 4) * Math.sin(segr / 4) / Math.sin(segr / 2); var cpsir1 = cpsi * r1; var cpsir2 = cpsi * r2; var spsir1 = spsi * r1; var spsir2 = spsi * r2; var mc = Math.cos(s1); var ms = Math.sin(s1); var x2 = -t * (cpsir1 * ms + spsir2 * mc); var y2 = -t * (spsir1 * ms - cpsir2 * mc); var x3 = 0; var y3 = 0; var result = []; for (var n = 0; n < seg; ++n) { s1 += segr; mc = Math.cos(s1); ms = Math.sin(s1); x3 = cpsir1 * mc - spsir2 * ms + tx; y3 = spsir1 * mc + cpsir2 * ms + ty; var dx = -t * (cpsir1 * ms + spsir2 * mc); var dy = -t * (spsir1 * ms - cpsir2 * mc); // CurveTo updates x0, y0 so need to restore it var index = n * 6; result[index] = Number(x2 + x0); result[index + 1] = Number(y2 + y0); result[index + 2] = Number(x3 - dx + x0); result[index + 3] = Number(y3 - dy + y0); result[index + 4] = Number(x3 + x0); result[index + 5] = Number(y3 + y0); x2 = x3 + dx; y2 = y3 + dy; } return result; }, /** * Function: getBoundingBox * * Returns the bounding box for the rotated rectangle. */ getBoundingBox: function(rect, rotation) { var result = null; if (rect != null && rotation != null && rotation != 0) { var rad = mxUtils.toRadians(rotation); var cos = Math.cos(rad); var sin = Math.sin(rad); var cx = new mxPoint( rect.x + rect.width / 2, rect.y + rect.height / 2); var p1 = new mxPoint(rect.x, rect.y); var p2 = new mxPoint(rect.x + rect.width, rect.y); var p3 = new mxPoint(p2.x, rect.y + rect.height); var p4 = new mxPoint(rect.x, p3.y); p1 = mxUtils.getRotatedPoint(p1, cos, sin, cx); p2 = mxUtils.getRotatedPoint(p2, cos, sin, cx); p3 = mxUtils.getRotatedPoint(p3, cos, sin, cx); p4 = mxUtils.getRotatedPoint(p4, cos, sin, cx); result = new mxRectangle(p1.x, p1.y, 0, 0); result.add(new mxRectangle(p2.x, p2.y, 0, 0)); result.add(new mxRectangle(p3.x, p3.y, 0, 0)); result.add(new mxRectangle(p4.x, p4.y, 0, 0)); } return result; }, /** * Function: getRotatedPoint * * Rotates the given point by the given cos and sin. */ getRotatedPoint: function(pt, cos, sin, c) { c = (c != null) ? c : new mxPoint(); var x = pt.x - c.x; var y = pt.y - c.y; var x1 = x * cos - y * sin; var y1 = y * cos + x * sin; return new mxPoint(x1 + c.x, y1 + c.y); }, /** * Returns an integer mask of the port constraints of the given map * @param dict the style map to determine the port constraints for * @param defaultValue Default value to return if the key is undefined. * @return the mask of port constraint directions * * Parameters: * * terminal - that represents the terminal. * edge - that represents the edge. * source - Boolean that specifies if the terminal is the source terminal. * defaultValue - Default value to be returned. */ getPortConstraints: function(terminal, edge, source, defaultValue) { var value = mxUtils.getValue(terminal.style, mxConstants.STYLE_PORT_CONSTRAINT, null); if (value == null) { return defaultValue; } else { var directions = value.toString(); var returnValue = mxConstants.DIRECTION_MASK_NONE; if (directions.indexOf(mxConstants.DIRECTION_NORTH) >= 0) { returnValue |= mxConstants.DIRECTION_MASK_NORTH; } if (directions.indexOf(mxConstants.DIRECTION_WEST) >= 0) { returnValue |= mxConstants.DIRECTION_MASK_WEST; } if (directions.indexOf(mxConstants.DIRECTION_SOUTH) >= 0) { returnValue |= mxConstants.DIRECTION_MASK_SOUTH; } if (directions.indexOf(mxConstants.DIRECTION_EAST) >= 0) { returnValue |= mxConstants.DIRECTION_MASK_EAST; } return returnValue; } }, /** * Function: reversePortConstraints * * Reverse the port constraint bitmask. For example, north | east * becomes south | west */ reversePortConstraints: function(constraint) { var result = 0; result = (constraint & mxConstants.DIRECTION_MASK_WEST) << 3; result |= (constraint & mxConstants.DIRECTION_MASK_NORTH) << 1; result |= (constraint & mxConstants.DIRECTION_MASK_SOUTH) >> 1; result |= (constraint & mxConstants.DIRECTION_MASK_EAST) >> 3; return result; }, /** * Function: findNearestSegment * * Finds the index of the nearest segment on the given cell state for * the specified coordinate pair. */ findNearestSegment: function(state, x, y) { var index = -1; if (state.absolutePoints.length > 0) { var last = state.absolutePoints[0]; var min = null; for (var i = 1; i < state.absolutePoints.length; i++) { var current = state.absolutePoints[i]; var dist = mxUtils.ptSegDistSq(last.x, last.y, current.x, current.y, x, y); if (min == null || dist < min) { min = dist; index = i - 1; } last = current; } } return index; }, /** * Function: rectangleIntersectsSegment * * Returns true if the given rectangle intersects the given segment. * * Parameters: * * bounds - that represents the rectangle. * p1 - that represents the first point of the segment. * p2 - that represents the second point of the segment. */ rectangleIntersectsSegment: function(bounds, p1, p2) { var top = bounds.y; var left = bounds.x; var bottom = top + bounds.height; var right = left + bounds.width; // Find min and max X for the segment var minX = p1.x; var maxX = p2.x; if (p1.x > p2.x) { minX = p2.x; maxX = p1.x; } // Find the intersection of the segment's and rectangle's x-projections if (maxX > right) { maxX = right; } if (minX < left) { minX = left; } if (minX > maxX) // If their projections do not intersect return false { return false; } // Find corresponding min and max Y for min and max X we found before var minY = p1.y; var maxY = p2.y; var dx = p2.x - p1.x; if (Math.abs(dx) > 0.0000001) { var a = (p2.y - p1.y) / dx; var b = p1.y - a * p1.x; minY = a * minX + b; maxY = a * maxX + b; } if (minY > maxY) { var tmp = maxY; maxY = minY; minY = tmp; } // Find the intersection of the segment's and rectangle's y-projections if (maxY > bottom) { maxY = bottom; } if (minY < top) { minY = top; } if (minY > maxY) // If Y-projections do not intersect return false { return false; } return true; }, /** * Function: contains * * Returns true if the specified point (x, y) is contained in the given rectangle. * * Parameters: * * bounds - that represents the area. * x - X-coordinate of the point. * y - Y-coordinate of the point. */ contains: function(bounds, x, y) { return (bounds.x <= x && bounds.x + bounds.width >= x && bounds.y <= y && bounds.y + bounds.height >= y); }, /** * Function: intersects * * Returns true if the two rectangles intersect. * * Parameters: * * a - to be checked for intersection. * b - to be checked for intersection. */ intersects: function(a, b) { var tw = a.width; var th = a.height; var rw = b.width; var rh = b.height; if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0) { return false; } var tx = a.x; var ty = a.y; var rx = b.x; var ry = b.y; rw += rx; rh += ry; tw += tx; th += ty; return ((rw < rx || rw > tx) && (rh < ry || rh > ty) && (tw < tx || tw > rx) && (th < ty || th > ry)); }, /** * Function: intersects * * Returns true if the two rectangles intersect. * * Parameters: * * a - to be checked for intersection. * b - to be checked for intersection. */ intersectsHotspot: function(state, x, y, hotspot, min, max) { hotspot = (hotspot != null) ? hotspot : 1; min = (min != null) ? min : 0; max = (max != null) ? max : 0; if (hotspot > 0) { var cx = state.getCenterX(); var cy = state.getCenterY(); var w = state.width; var h = state.height; var start = mxUtils.getValue(state.style, mxConstants.STYLE_STARTSIZE) * state.view.scale; if (start > 0) { if (mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, true)) { cy = state.y + start / 2; h = start; } else { cx = state.x + start / 2; w = start; } } w = Math.max(min, w * hotspot); h = Math.max(min, h * hotspot); if (max > 0) { w = Math.min(w, max); h = Math.min(h, max); } var rect = new mxRectangle(cx - w / 2, cy - h / 2, w, h); return mxUtils.contains(rect, x, y); } return true; }, /** * Function: getOffset * * Returns the offset for the specified container as an . The * offset is the distance from the top left corner of the container to the * top left corner of the document. * * Parameters: * * container - DOM node to return the offset for. * scollOffset - Optional boolean to add the scroll offset of the document. * Default is false. */ getOffset: function(container, scrollOffset) { var offsetLeft = 0; var offsetTop = 0; if (scrollOffset != null && scrollOffset) { var b = document.body; var d = document.documentElement; offsetLeft += (b.scrollLeft || d.scrollLeft); offsetTop += (b.scrollTop || d.scrollTop); } while (container.offsetParent) { offsetLeft += container.offsetLeft; offsetTop += container.offsetTop; container = container.offsetParent; } return new mxPoint(offsetLeft, offsetTop); }, /** * Function: getScrollOrigin * * Returns the top, left corner of the viewrect as an . */ getScrollOrigin: function(node) { var b = document.body; var d = document.documentElement; var sl = (b.scrollLeft || d.scrollLeft); var st = (b.scrollTop || d.scrollTop); var result = new mxPoint(sl, st); while (node != null && node != b && node != d) { if (!isNaN(node.scrollLeft) && !isNaN(node.scrollTop)) { result.x += node.scrollLeft; result.y += node.scrollTop; } node = node.parentNode; } return result; }, /** * Function: convertPoint * * Converts the specified point (x, y) using the offset of the specified * container and returns a new with the result. * * Parameters: * * container - DOM node to use for the offset. * x - X-coordinate of the point to be converted. * y - Y-coordinate of the point to be converted. */ convertPoint: function(container, x, y) { var origin = mxUtils.getScrollOrigin(container); var offset = mxUtils.getOffset(container); offset.x -= origin.x; offset.y -= origin.y; return new mxPoint(x - offset.x, y - offset.y); }, /** * Function: ltrim * * Strips all whitespaces from the beginning of the string. * Without the second parameter, Javascript function will trim these * characters: * * - " " (ASCII 32 (0x20)), an ordinary space * - "\t" (ASCII 9 (0x09)), a tab * - "\n" (ASCII 10 (0x0A)), a new line (line feed) * - "\r" (ASCII 13 (0x0D)), a carriage return * - "\0" (ASCII 0 (0x00)), the NUL-byte * - "\x0B" (ASCII 11 (0x0B)), a vertical tab */ ltrim: function(str, chars) { chars = chars || "\\s"; return str.replace(new RegExp("^[" + chars + "]+", "g"), ""); }, /** * Function: rtrim * * Strips all whitespaces from the end of the string. * Without the second parameter, Javascript function will trim these * characters: * * - " " (ASCII 32 (0x20)), an ordinary space * - "\t" (ASCII 9 (0x09)), a tab * - "\n" (ASCII 10 (0x0A)), a new line (line feed) * - "\r" (ASCII 13 (0x0D)), a carriage return * - "\0" (ASCII 0 (0x00)), the NUL-byte * - "\x0B" (ASCII 11 (0x0B)), a vertical tab */ rtrim: function(str, chars) { chars = chars || "\\s"; return str.replace(new RegExp("[" + chars + "]+$", "g"), ""); }, /** * Function: trim * * Strips all whitespaces from both end of the string. * Without the second parameter, Javascript function will trim these * characters: * * - " " (ASCII 32 (0x20)), an ordinary space * - "\t" (ASCII 9 (0x09)), a tab * - "\n" (ASCII 10 (0x0A)), a new line (line feed) * - "\r" (ASCII 13 (0x0D)), a carriage return * - "\0" (ASCII 0 (0x00)), the NUL-byte * - "\x0B" (ASCII 11 (0x0B)), a vertical tab */ trim: function(str, chars) { return mxUtils.ltrim(mxUtils.rtrim(str, chars), chars); }, /** * Function: isNumeric * * Returns true if the specified value is numeric, that is, if it is not * null, not an empty string, not a HEX number and isNaN returns false. * * Parameters: * * str - String representing the possibly numeric value. */ isNumeric: function(str) { return str != null && (str.length == null || (str.length > 0 && str.indexOf('0x') < 0) && str.indexOf('0X') < 0) && !isNaN(str); }, /** * Function: mod * * Returns the remainder of division of n by m. You should use this instead * of the built-in operation as the built-in operation does not properly * handle negative numbers. */ mod: function(n, m) { return ((n % m) + m) % m; }, /** * Function: intersection * * Returns the intersection of two lines as an . * * Parameters: * * x0 - X-coordinate of the first line's startpoint. * y0 - X-coordinate of the first line's startpoint. * x1 - X-coordinate of the first line's endpoint. * y1 - Y-coordinate of the first line's endpoint. * x2 - X-coordinate of the second line's startpoint. * y2 - Y-coordinate of the second line's startpoint. * x3 - X-coordinate of the second line's endpoint. * y3 - Y-coordinate of the second line's endpoint. */ intersection: function (x0, y0, x1, y1, x2, y2, x3, y3) { var denom = ((y3 - y2)*(x1 - x0)) - ((x3 - x2)*(y1 - y0)); var nume_a = ((x3 - x2)*(y0 - y2)) - ((y3 - y2)*(x0 - x2)); var nume_b = ((x1 - x0)*(y0 - y2)) - ((y1 - y0)*(x0 - x2)); var ua = nume_a / denom; var ub = nume_b / denom; if(ua >= 0.0 && ua <= 1.0 && ub >= 0.0 && ub <= 1.0) { // Get the intersection point var intersectionX = x0 + ua*(x1 - x0); var intersectionY = y0 + ua*(y1 - y0); return new mxPoint(intersectionX, intersectionY); } // No intersection return null; }, /** * Function: ptSeqDistSq * * Returns the square distance between a segment and a point. * * Parameters: * * x1 - X-coordinate of the startpoint of the segment. * y1 - Y-coordinate of the startpoint of the segment. * x2 - X-coordinate of the endpoint of the segment. * y2 - Y-coordinate of the endpoint of the segment. * px - X-coordinate of the point. * py - Y-coordinate of the point. */ ptSegDistSq: function(x1, y1, x2, y2, px, py) { x2 -= x1; y2 -= y1; px -= x1; py -= y1; var dotprod = px * x2 + py * y2; var projlenSq; if (dotprod <= 0.0) { projlenSq = 0.0; } else { px = x2 - px; py = y2 - py; dotprod = px * x2 + py * y2; if (dotprod <= 0.0) { projlenSq = 0.0; } else { projlenSq = dotprod * dotprod / (x2 * x2 + y2 * y2); } } var lenSq = px * px + py * py - projlenSq; if (lenSq < 0) { lenSq = 0; } return lenSq; }, /** * Function: relativeCcw * * Returns 1 if the given point on the right side of the segment, 0 if its * on the segment, and -1 if the point is on the left side of the segment. * * Parameters: * * x1 - X-coordinate of the startpoint of the segment. * y1 - Y-coordinate of the startpoint of the segment. * x2 - X-coordinate of the endpoint of the segment. * y2 - Y-coordinate of the endpoint of the segment. * px - X-coordinate of the point. * py - Y-coordinate of the point. */ relativeCcw: function(x1, y1, x2, y2, px, py) { x2 -= x1; y2 -= y1; px -= x1; py -= y1; var ccw = px * y2 - py * x2; if (ccw == 0.0) { ccw = px * x2 + py * y2; if (ccw > 0.0) { px -= x2; py -= y2; ccw = px * x2 + py * y2; if (ccw < 0.0) { ccw = 0.0; } } } return (ccw < 0.0) ? -1 : ((ccw > 0.0) ? 1 : 0); }, /** * Function: animateChanges * * See . This is for backwards compatibility and * will be removed later. */ animateChanges: function(graph, changes) { // LATER: Deprecated, remove this function mxEffects.animateChanges.apply(this, arguments); }, /** * Function: cascadeOpacity * * See . This is for backwards compatibility and * will be removed later. */ cascadeOpacity: function(graph, cell, opacity) { mxEffects.cascadeOpacity.apply(this, arguments); }, /** * Function: fadeOut * * See . This is for backwards compatibility and * will be removed later. */ fadeOut: function(node, from, remove, step, delay, isEnabled) { mxEffects.fadeOut.apply(this, arguments); }, /** * Function: setOpacity * * Sets the opacity of the specified DOM node to the given value in %. * * Parameters: * * node - DOM node to set the opacity for. * value - Opacity in %. Possible values are between 0 and 100. */ setOpacity: function(node, value) { if (mxUtils.isVml(node)) { if (value >= 100) { node.style.filter = null; } else { // TODO: Why is the division by 5 needed in VML? node.style.filter = 'alpha(opacity=' + (value/5) + ')'; } } else if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9)) { if (value >= 100) { node.style.filter = null; } else { node.style.filter = 'alpha(opacity=' + value + ')'; } } else { node.style.opacity = (value / 100); } }, /** * Function: createImage * * Creates and returns an image (IMG node) or VML image (v:image) in IE6 in * quirs mode. * * Parameters: * * src - URL that points to the image to be displayed. */ createImage: function(src) { var imageNode = null; if (mxClient.IS_IE6 && document.compatMode != 'CSS1Compat') { imageNode = document.createElement('v:image'); imageNode.setAttribute('src', src); imageNode.style.borderStyle = 'none'; } else { imageNode = document.createElement('img'); imageNode.setAttribute('src', src); imageNode.setAttribute('border', '0'); } return imageNode; }, /** * Function: sortCells * * Sorts the given cells according to the order in the cell hierarchy. * Ascending is optional and defaults to true. */ sortCells: function(cells, ascending) { ascending = (ascending != null) ? ascending : true; var lookup = new mxDictionary(); cells.sort(function(o1, o2) { var p1 = lookup.get(o1); if (p1 == null) { p1 = mxCellPath.create(o1).split(mxCellPath.PATH_SEPARATOR); lookup.put(o1, p1); } var p2 = lookup.get(o2); if (p2 == null) { p2 = mxCellPath.create(o2).split(mxCellPath.PATH_SEPARATOR); lookup.put(o2, p2); } var comp = mxCellPath.compare(p1, p2); return (comp == 0) ? 0 : (((comp > 0) == ascending) ? 1 : -1); }); return cells; }, /** * Function: getStylename * * Returns the stylename in a style of the form [(stylename|key=value);] or * an empty string if the given style does not contain a stylename. * * Parameters: * * style - String of the form [(stylename|key=value);]. */ getStylename: function(style) { if (style != null) { var pairs = style.split(';'); var stylename = pairs[0]; if (stylename.indexOf('=') < 0) { return stylename; } } return ''; }, /** * Function: getStylenames * * Returns the stylenames in a style of the form [(stylename|key=value);] * or an empty array if the given style does not contain any stylenames. * * Parameters: * * style - String of the form [(stylename|key=value);]. */ getStylenames: function(style) { var result = []; if (style != null) { var pairs = style.split(';'); for (var i = 0; i < pairs.length; i++) { if (pairs[i].indexOf('=') < 0) { result.push(pairs[i]); } } } return result; }, /** * Function: indexOfStylename * * Returns the index of the given stylename in the given style. This * returns -1 if the given stylename does not occur (as a stylename) in the * given style, otherwise it returns the index of the first character. */ indexOfStylename: function(style, stylename) { if (style != null && stylename != null) { var tokens = style.split(';'); var pos = 0; for (var i = 0; i < tokens.length; i++) { if (tokens[i] == stylename) { return pos; } pos += tokens[i].length + 1; } } return -1; }, /** * Function: addStylename * * Adds the specified stylename to the given style if it does not already * contain the stylename. */ addStylename: function(style, stylename) { if (mxUtils.indexOfStylename(style, stylename) < 0) { if (style == null) { style = ''; } else if (style.length > 0 && style.charAt(style.length - 1) != ';') { style += ';'; } style += stylename; } return style; }, /** * Function: removeStylename * * Removes all occurrences of the specified stylename in the given style * and returns the updated style. Trailing semicolons are not preserved. */ removeStylename: function(style, stylename) { var result = []; if (style != null) { var tokens = style.split(';'); for (var i = 0; i < tokens.length; i++) { if (tokens[i] != stylename) { result.push(tokens[i]); } } } return result.join(';'); }, /** * Function: removeAllStylenames * * Removes all stylenames from the given style and returns the updated * style. */ removeAllStylenames: function(style) { var result = []; if (style != null) { var tokens = style.split(';'); for (var i = 0; i < tokens.length; i++) { // Keeps the key, value assignments if (tokens[i].indexOf('=') >= 0) { result.push(tokens[i]); } } } return result.join(';'); }, /** * Function: setCellStyles * * Assigns the value for the given key in the styles of the given cells, or * removes the key from the styles if the value is null. * * Parameters: * * model - to execute the transaction in. * cells - Array of to be updated. * key - Key of the style to be changed. * value - New value for the given key. */ setCellStyles: function(model, cells, key, value) { if (cells != null && cells.length > 0) { model.beginUpdate(); try { for (var i = 0; i < cells.length; i++) { if (cells[i] != null) { var style = mxUtils.setStyle( model.getStyle(cells[i]), key, value); model.setStyle(cells[i], style); } } } finally { model.endUpdate(); } } }, /** * Function: setStyle * * Adds or removes the given key, value pair to the style and returns the * new style. If value is null or zero length then the key is removed from * the style. This is for cell styles, not for CSS styles. * * Parameters: * * style - String of the form [(stylename|key=value);]. * key - Key of the style to be changed. * value - New value for the given key. */ setStyle: function(style, key, value) { var isValue = value != null && (typeof(value.length) == 'undefined' || value.length > 0); if (style == null || style.length == 0) { if (isValue) { style = key+'='+value; } } else { var index = style.indexOf(key+'='); if (index < 0) { if (isValue) { var sep = (style.charAt(style.length-1) == ';') ? '' : ';'; style = style + sep + key+'='+value; } } else { var tmp = (isValue) ? (key + '=' + value) : ''; var cont = style.indexOf(';', index); if (!isValue) { cont++; } style = style.substring(0, index) + tmp + ((cont > index) ? style.substring(cont) : ''); } } return style; }, /** * Function: setCellStyleFlags * * Sets or toggles the flag bit for the given key in the cell's styles. * If value is null then the flag is toggled. * * Example: * * (code) * var cells = graph.getSelectionCells(); * mxUtils.setCellStyleFlags(graph.model, * cells, * mxConstants.STYLE_FONTSTYLE, * mxConstants.FONT_BOLD); * (end) * * Toggles the bold font style. * * Parameters: * * model - that contains the cells. * cells - Array of to change the style for. * key - Key of the style to be changed. * flag - Integer for the bit to be changed. * value - Optional boolean value for the flag. */ setCellStyleFlags: function(model, cells, key, flag, value) { if (cells != null && cells.length > 0) { model.beginUpdate(); try { for (var i = 0; i < cells.length; i++) { if (cells[i] != null) { var style = mxUtils.setStyleFlag( model.getStyle(cells[i]), key, flag, value); model.setStyle(cells[i], style); } } } finally { model.endUpdate(); } } }, /** * Function: setStyleFlag * * Sets or removes the given key from the specified style and returns the * new style. If value is null then the flag is toggled. * * Parameters: * * style - String of the form [(stylename|key=value);]. * key - Key of the style to be changed. * flag - Integer for the bit to be changed. * value - Optional boolean value for the given flag. */ setStyleFlag: function(style, key, flag, value) { if (style == null || style.length == 0) { if (value || value == null) { style = key+'='+flag; } else { style = key+'=0'; } } else { var index = style.indexOf(key+'='); if (index < 0) { var sep = (style.charAt(style.length-1) == ';') ? '' : ';'; if (value || value == null) { style = style + sep + key + '=' + flag; } else { style = style + sep + key + '=0'; } } else { var cont = style.indexOf(';', index); var tmp = ''; if (cont < 0) { tmp = style.substring(index+key.length+1); } else { tmp = style.substring(index+key.length+1, cont); } if (value == null) { tmp = parseInt(tmp) ^ flag; } else if (value) { tmp = parseInt(tmp) | flag; } else { tmp = parseInt(tmp) & ~flag; } style = style.substring(0, index) + key + '=' + tmp + ((cont >= 0) ? style.substring(cont) : ''); } } return style; }, /** * Function: getSizeForString * * Returns an with the size (width and height in pixels) of * the given string. The string may contain HTML markup. Newlines should be * converted to
before calling this method. * * Example: * * (code) * var label = graph.getLabel(cell).replace(/\n/g, "
"); * var size = graph.getSizeForString(label); * (end) * * Parameters: * * text - String whose size should be returned. * fontSize - Integer that specifies the font size in pixels. Default is * . * fontFamily - String that specifies the name of the font family. Default * is . */ getSizeForString: function(text, fontSize, fontFamily) { var div = document.createElement('div'); // Sets the font size and family if non-default div.style.fontSize = (fontSize || mxConstants.DEFAULT_FONTSIZE) + 'px'; div.style.fontFamily = fontFamily || mxConstants.DEFAULT_FONTFAMILY; // Disables block layout and outside wrapping and hides the div div.style.position = 'absolute'; div.style.display = 'inline'; div.style.visibility = 'hidden'; // Adds the text and inserts into DOM for updating of size div.innerHTML = text; document.body.appendChild(div); // Gets the size and removes from DOM var size = new mxRectangle(0, 0, div.offsetWidth, div.offsetHeight); document.body.removeChild(div); return size; }, /** * Function: getViewXml */ getViewXml: function(graph, scale, cells, x0, y0) { x0 = (x0 != null) ? x0 : 0; y0 = (y0 != null) ? y0 : 0; scale = (scale != null) ? scale : 1; if (cells == null) { var model = graph.getModel(); cells = [model.getRoot()]; } var view = graph.getView(); var result = null; // Disables events on the view var eventsEnabled = view.isEventsEnabled(); view.setEventsEnabled(false); // Workaround for label bounds not taken into account for image export. // Creates a temporary draw pane which is used for rendering the text. // Text rendering is required for finding the bounds of the labels. var drawPane = view.drawPane; var overlayPane = view.overlayPane; if (graph.dialect == mxConstants.DIALECT_SVG) { view.drawPane = document.createElementNS(mxConstants.NS_SVG, 'g'); view.canvas.appendChild(view.drawPane); // Redirects cell overlays into temporary container view.overlayPane = document.createElementNS(mxConstants.NS_SVG, 'g'); view.canvas.appendChild(view.overlayPane); } else { view.drawPane = view.drawPane.cloneNode(false); view.canvas.appendChild(view.drawPane); // Redirects cell overlays into temporary container view.overlayPane = view.overlayPane.cloneNode(false); view.canvas.appendChild(view.overlayPane); } // Resets the translation var translate = view.getTranslate(); view.translate = new mxPoint(x0, y0); // Creates the temporary cell states in the view var temp = new mxTemporaryCellStates(graph.getView(), scale, cells); try { var enc = new mxCodec(); result = enc.encode(graph.getView()); } finally { temp.destroy(); view.translate = translate; view.canvas.removeChild(view.drawPane); view.canvas.removeChild(view.overlayPane); view.drawPane = drawPane; view.overlayPane = overlayPane; view.setEventsEnabled(eventsEnabled); } return result; }, /** * Function: getScaleForPageCount * * Returns the scale to be used for printing the graph with the given * bounds across the specifies number of pages with the given format. The * scale is always computed such that it given the given amount or fewer * pages in the print output. See for an example. * * Parameters: * * pageCount - Specifies the number of pages in the print output. * graph - that should be printed. * pageFormat - Optional that specifies the page format. * Default is . * border - The border along each side of every page. */ getScaleForPageCount: function(pageCount, graph, pageFormat, border) { if (pageCount < 1) { // We can't work with less than 1 page, return no scale // change return 1; } pageFormat = (pageFormat != null) ? pageFormat : mxConstants.PAGE_FORMAT_A4_PORTRAIT; border = (border != null) ? border : 0; var availablePageWidth = pageFormat.width - (border * 2); var availablePageHeight = pageFormat.height - (border * 2); // Work out the number of pages required if the // graph is not scaled. var graphBounds = graph.getGraphBounds().clone(); var sc = graph.getView().getScale(); graphBounds.width /= sc; graphBounds.height /= sc; var graphWidth = graphBounds.width; var graphHeight = graphBounds.height; var scale = 1; // The ratio of the width/height for each printer page var pageFormatAspectRatio = availablePageWidth / availablePageHeight; // The ratio of the width/height for the graph to be printer var graphAspectRatio = graphWidth / graphHeight; // The ratio of horizontal pages / vertical pages for this // graph to maintain its aspect ratio on this page format var pagesAspectRatio = graphAspectRatio / pageFormatAspectRatio; // Factor the square root of the page count up and down // by the pages aspect ratio to obtain a horizontal and // vertical page count that adds up to the page count // and has the correct aspect ratio var pageRoot = Math.sqrt(pageCount); var pagesAspectRatioSqrt = Math.sqrt(pagesAspectRatio); var numRowPages = pageRoot * pagesAspectRatioSqrt; var numColumnPages = pageRoot / pagesAspectRatioSqrt; // These value are rarely more than 2 rounding downs away from // a total that meets the page count. In cases of one being less // than 1 page, the other value can be too high and take more iterations // In this case, just change that value to be the page count, since // we know the other value is 1 if (numRowPages < 1 && numColumnPages > pageCount) { var scaleChange = numColumnPages / pageCount; numColumnPages = pageCount; numRowPages /= scaleChange; } if (numColumnPages < 1 && numRowPages > pageCount) { var scaleChange = numRowPages / pageCount; numRowPages = pageCount; numColumnPages /= scaleChange; } var currentTotalPages = Math.ceil(numRowPages) * Math.ceil(numColumnPages); var numLoops = 0; // Iterate through while the rounded up number of pages comes to // a total greater than the required number while (currentTotalPages > pageCount) { // Round down the page count (rows or columns) that is // closest to its next integer down in percentage terms. // i.e. Reduce the page total by reducing the total // page area by the least possible amount var roundRowDownProportion = Math.floor(numRowPages) / numRowPages; var roundColumnDownProportion = Math.floor(numColumnPages) / numColumnPages; // If the round down proportion is, work out the proportion to // round down to 1 page less if (roundRowDownProportion == 1) { roundRowDownProportion = Math.floor(numRowPages-1) / numRowPages; } if (roundColumnDownProportion == 1) { roundColumnDownProportion = Math.floor(numColumnPages-1) / numColumnPages; } // Check which rounding down is smaller, but in the case of very small roundings // try the other dimension instead var scaleChange = 1; // Use the higher of the two values if (roundRowDownProportion > roundColumnDownProportion) { scaleChange = roundRowDownProportion; } else { scaleChange = roundColumnDownProportion; } numRowPages = numRowPages * scaleChange; numColumnPages = numColumnPages * scaleChange; currentTotalPages = Math.ceil(numRowPages) * Math.ceil(numColumnPages); numLoops++; if (numLoops > 10) { break; } } // Work out the scale from the number of row pages required // The column pages will give the same value var posterWidth = availablePageWidth * numRowPages; scale = posterWidth / graphWidth; // Allow for rounding errors return scale * 0.99999; }, /** * Function: show * * Copies the styles and the markup from the graph's container into the * given document and removes all cursor styles. The document is returned. * * This function should be called from within the document with the graph. * If you experience problems with missing stylesheets in IE then try adding * the domain to the trusted sites. * * Parameters: * * graph - to be copied. * doc - Document where the new graph is created. * x0 - X-coordinate of the graph view origin. Default is 0. * y0 - Y-coordinate of the graph view origin. Default is 0. */ show: function(graph, doc, x0, y0) { x0 = (x0 != null) ? x0 : 0; y0 = (y0 != null) ? y0 : 0; if (doc == null) { var wnd = window.open(); doc = wnd.document; } else { doc.open(); } var bounds = graph.getGraphBounds(); var dx = -bounds.x + x0; var dy = -bounds.y + y0; // Needs a special way of creating the page so that no click is required // to refresh the contents after the external CSS styles have been loaded. // To avoid a click or programmatic refresh, the styleSheets[].cssText // property is copied over from the original document. if (mxClient.IS_IE) { var html = ''; html += ''; var base = document.getElementsByTagName('base'); for (var i = 0; i < base.length; i++) { html += base[i].outerHTML; } html += ''; html += ''; html += ''; // Copies the contents of the graph container html += graph.container.innerHTML; html += ''; html += ''; doc.writeln(html); doc.close(); // Makes sure the inner container is on the top, left var node = doc.body.getElementsByTagName('DIV')[0]; if (node != null) { node.style.position = 'absolute'; node.style.left = dx + 'px'; node.style.top = dy + 'px'; } } else { doc.writeln(''); var base = document.getElementsByTagName('base'); for (var i=0; i'); doc.writeln(''); doc.close(); // Workaround for FF2 which has no body element in a document where // the body has been added using document.write. if (doc.body == null) { doc.documentElement.appendChild(doc.createElement('body')); } // Workaround for missing scrollbars in FF doc.body.style.overflow = 'auto'; var node = graph.container.firstChild; while (node != null) { var clone = node.cloneNode(true); doc.body.appendChild(clone); node = node.nextSibling; } // Shifts negative coordinates into visible space var node = doc.getElementsByTagName('g')[0]; if (node != null) { node.setAttribute('transform', 'translate(' + dx + ',' + dy + ')'); // Updates the size of the SVG container var root = node.ownerSVGElement; root.setAttribute('width', bounds.width + Math.max(bounds.x, 0) + 3); root.setAttribute('height', bounds.height + Math.max(bounds.y, 0) + 3); } } mxUtils.removeCursors(doc.body); return doc; }, /** * Function: printScreen * * Prints the specified graph using a new window and the built-in print * dialog. * * This function should be called from within the document with the graph. * * Parameters: * * graph - to be printed. */ printScreen: function(graph) { var wnd = window.open(); mxUtils.show(graph, wnd.document); var print = function() { wnd.focus(); wnd.print(); wnd.close(); }; // Workaround for Google Chrome which needs a bit of a // delay in order to render the SVG contents if (mxClient.IS_GC) { wnd.setTimeout(print, 500); } else { print(); } }, /** * Function: popup * * Shows the specified text content in a new or a new browser * window if isInternalWindow is false. * * Parameters: * * content - String that specifies the text to be displayed. * isInternalWindow - Optional boolean indicating if an mxWindow should be * used instead of a new browser window. Default is false. */ popup: function(content, isInternalWindow) { if (isInternalWindow) { var div = document.createElement('div'); div.style.overflow = 'scroll'; div.style.width = '636px'; div.style.height = '460px'; var pre = document.createElement('pre'); pre.innerHTML = mxUtils.htmlEntities(content, false). replace(/\n/g,'
').replace(/ /g, ' '); div.appendChild(pre); var w = document.body.clientWidth; var h = (document.body.clientHeight || document.documentElement.clientHeight); var wnd = new mxWindow('Popup Window', div, w/2-320, h/2-240, 640, 480, false, true); wnd.setClosable(true); wnd.setVisible(true); } else { // Wraps up the XML content in a textarea if (mxClient.IS_NS) { var wnd = window.open(); wnd.document.writeln('
'+mxUtils.htmlEntities(content)+'').replace(/ /g, ' ');
			   	wnd.document.body.appendChild(pre);
			}
	   	}
	},
	
	/**
	 * Function: alert
	 * 
	 * Displayss the given alert in a new dialog. This implementation uses the
	 * built-in alert function. This is used to display validation errors when
	 * connections cannot be changed or created.
	 * 
	 * Parameters:
	 * 
	 * message - String specifying the message to be displayed.
	 */
	alert: function(message)
	{
		alert(message);
	},
	
	/**
	 * Function: prompt
	 * 
	 * Displays the given message in a prompt dialog. This implementation uses
	 * the built-in prompt function.
	 * 
	 * Parameters:
	 * 
	 * message - String specifying the message to be displayed.
	 * defaultValue - Optional string specifying the default value.
	 */
	prompt: function(message, defaultValue)
	{
		return prompt(message, defaultValue);
	},
	
	/**
	 * Function: confirm
	 * 
	 * Displays the given message in a confirm dialog. This implementation uses
	 * the built-in confirm function.
	 * 
	 * Parameters:
	 * 
	 * message - String specifying the message to be displayed.
	 */
	confirm: function(message)
	{
		return confirm(message);
	},

	/**
	 * Function: error
	 * 
	 * Displays the given error message in a new  of the given width.
	 * If close is true then an additional close button is added to the window.
	 * The optional icon specifies the icon to be used for the window. Default
	 * is .
	 * 
	 * Parameters:
	 * 
	 * message - String specifying the message to be displayed.
	 * width - Integer specifying the width of the window.
	 * close - Optional boolean indicating whether to add a close button.
	 * icon - Optional icon for the window decoration.
	 */
	error: function(message, width, close, icon)
	{
		var div = document.createElement('div');
		div.style.padding = '20px';

		var img = document.createElement('img');
		img.setAttribute('src', icon || mxUtils.errorImage);
		img.setAttribute('valign', 'bottom');
		img.style.verticalAlign = 'middle';
		div.appendChild(img);

		div.appendChild(document.createTextNode('\u00a0')); //  
		div.appendChild(document.createTextNode('\u00a0')); //  
		div.appendChild(document.createTextNode('\u00a0')); //  
		mxUtils.write(div, message);

		var w = document.body.clientWidth;
		var h = (document.body.clientHeight || document.documentElement.clientHeight);
		var warn = new mxWindow(mxResources.get(mxUtils.errorResource) ||
			mxUtils.errorResource, div, (w-width)/2, h/4, width, null,
			false, true);

		if (close)
		{
			mxUtils.br(div);
			
			var tmp = document.createElement('p');
			var button = document.createElement('button');

			if (mxClient.IS_IE)
			{
				button.style.cssText = 'float:right';
			}
			else
			{
				button.setAttribute('style', 'float:right');
			}

			mxEvent.addListener(button, 'click', function(evt)
			{
				warn.destroy();
			});

			mxUtils.write(button, mxResources.get(mxUtils.closeResource) ||
				mxUtils.closeResource);
			
			tmp.appendChild(button);
			div.appendChild(tmp);
			
			mxUtils.br(div);
			
			warn.setClosable(true);
		}
		
		warn.setVisible(true);
		
		return warn;
	},

	/**
	 * Function: makeDraggable
	 * 
	 * Configures the given DOM element to act as a drag source for the
	 * specified graph. Returns a a new . If
	 *  is enabled then the x and y arguments must
	 * be used in funct to match the preview location.
	 * 
	 * Example:
	 * 
	 * (code)
	 * var funct = function(graph, evt, cell, x, y)
	 * {
	 *   if (graph.canImportCell(cell))
	 *   {
	 *     var parent = graph.getDefaultParent();
	 *     var vertex = null;
	 *     
	 *     graph.getModel().beginUpdate();
	 *     try
	 *     {
	 * 	     vertex = graph.insertVertex(parent, null, 'Hello', x, y, 80, 30);
	 *     }
	 *     finally
	 *     {
	 *       graph.getModel().endUpdate();
	 *     }
	 *
	 *     graph.setSelectionCell(vertex);
	 *   }
	 * }
	 * 
	 * var img = document.createElement('img');
	 * img.setAttribute('src', 'editors/images/rectangle.gif');
	 * img.style.position = 'absolute';
	 * img.style.left = '0px';
	 * img.style.top = '0px';
	 * img.style.width = '16px';
	 * img.style.height = '16px';
	 * 
	 * var dragImage = img.cloneNode(true);
	 * dragImage.style.width = '32px';
	 * dragImage.style.height = '32px';
	 * mxUtils.makeDraggable(img, graph, funct, dragImage);
	 * document.body.appendChild(img);
	 * (end)
	 * 
	 * Parameters:
	 * 
	 * element - DOM element to make draggable.
	 * graphF -  that acts as the drop target or a function that takes a
	 * mouse event and returns the current .
	 * funct - Function to execute on a successful drop.
	 * dragElement - Optional DOM node to be used for the drag preview.
	 * dx - Optional horizontal offset between the cursor and the drag
	 * preview.
	 * dy - Optional vertical offset between the cursor and the drag
	 * preview.
	 * autoscroll - Optional boolean that specifies if autoscroll should be
	 * used. Default is mxGraph.autoscroll.
	 * scalePreview - Optional boolean that specifies if the preview element
	 * should be scaled according to the graph scale. If this is true, then
	 * the offsets will also be scaled. Default is false.
	 * highlightDropTargets - Optional boolean that specifies if dropTargets
	 * should be highlighted. Default is true.
	 * getDropTarget - Optional function to return the drop target for a given
	 * location (x, y). Default is mxGraph.getCellAt.
	 */
	makeDraggable: function(element, graphF, funct, dragElement, dx, dy, autoscroll,
			scalePreview, highlightDropTargets, getDropTarget)
	{
		var dragSource = new mxDragSource(element, funct);
		dragSource.dragOffset = new mxPoint((dx != null) ? dx : 0,
			(dy != null) ? dy : mxConstants.TOOLTIP_VERTICAL_OFFSET);
		dragSource.autoscroll = autoscroll;
		
		// Cannot enable this by default. This needs to be enabled in the caller
		// if the funct argument uses the new x- and y-arguments.
		dragSource.setGuidesEnabled(false);
		
		if (highlightDropTargets != null)
		{
			dragSource.highlightDropTargets = highlightDropTargets;
		}
		
		// Overrides function to find drop target cell
		if (getDropTarget != null)
		{
			dragSource.getDropTarget = getDropTarget;
		}
		
		// Overrides function to get current graph
		dragSource.getGraphForEvent = function(evt)
		{
			return (typeof(graphF) == 'function') ? graphF(evt) : graphF;
		};
		
		// Translates switches into dragSource customizations
		if (dragElement != null)
		{
			dragSource.createDragElement = function()
			{
				return dragElement.cloneNode(true);
			};
			
			if (scalePreview)
			{
				dragSource.createPreviewElement = function(graph)
				{
					var elt = dragElement.cloneNode(true);

					var w = parseInt(elt.style.width);
					var h = parseInt(elt.style.height);
					elt.style.width = Math.round(w * graph.view.scale) + 'px';
					elt.style.height = Math.round(h * graph.view.scale) + 'px';
					
					return elt;
				};
			}
		}
		
		return dragSource;
	}

};
/**
 * $Id: mxConstants.js,v 1.128 2013-01-16 08:40:17 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
 var mxConstants =
 {
	/**
	 * Class: mxConstants
	 * 
	 * Defines various global constants.
	 * 
	 * Variable: DEFAULT_HOTSPOT
	 * 
	 * Defines the portion of the cell which is to be used as a connectable
	 * region. Default is 0.3. Possible values are 0 < x <= 1. 
	 */
	DEFAULT_HOTSPOT: 0.3,

	/**
	 * Variable: MIN_HOTSPOT_SIZE
	 * 
	 * Defines the minimum size in pixels of the portion of the cell which is
	 * to be used as a connectable region. Default is 8.
	 */
	MIN_HOTSPOT_SIZE: 8,

	/**
	 * Variable: MAX_HOTSPOT_SIZE
	 * 
	 * Defines the maximum size in pixels of the portion of the cell which is
	 * to be used as a connectable region. Use 0 for no maximum. Default is 0.
	 */
	MAX_HOTSPOT_SIZE: 0,

	/**
	 * Variable: RENDERING_HINT_EXACT
	 * 
	 * Defines the exact rendering hint.
	 */
	RENDERING_HINT_EXACT: 'exact',

	/**
	 * Variable: RENDERING_HINT_FASTER
	 * 
	 * Defines the faster rendering hint.
	 */
	RENDERING_HINT_FASTER: 'faster',

	/**
	 * Variable: RENDERING_HINT_FASTEST
	 * 
	 * Defines the fastest rendering hint.
	 */
	RENDERING_HINT_FASTEST: 'fastest',

	/**
	 * Variable: DIALECT_SVG
	 * 
	 * Defines the SVG display dialect name.
	 */
	DIALECT_SVG: 'svg',

	/**
	 * Variable: DIALECT_VML
	 * 
	 * Defines the VML display dialect name.
	 */
	DIALECT_VML: 'vml',

	/**
	 * Variable: DIALECT_MIXEDHTML
	 * 
	 * Defines the mixed HTML display dialect name.
	 */
	DIALECT_MIXEDHTML: 'mixedHtml',

	/**
	 * Variable: DIALECT_PREFERHTML
	 * 
	 * Defines the preferred HTML display dialect name.
	 */
	DIALECT_PREFERHTML: 'preferHtml',

	/**
	 * Variable: DIALECT_STRICTHTML
	 * 
	 * Defines the strict HTML display dialect.
	 */
	DIALECT_STRICTHTML: 'strictHtml',

	/**
	 * Variable: NS_SVG
	 * 
	 * Defines the SVG namespace.
	 */
	NS_SVG: 'http://www.w3.org/2000/svg',

	/**
	 * Variable: NS_XHTML
	 * 
	 * Defines the XHTML namespace.
	 */
	NS_XHTML: 'http://www.w3.org/1999/xhtml',

	/**
	 * Variable: NS_XLINK
	 * 
	 * Defines the XLink namespace.
	 */
	NS_XLINK: 'http://www.w3.org/1999/xlink',

	/**
	 * Variable: SHADOWCOLOR
	 * 
	 * Defines the color to be used to draw shadows in shapes and windows.
	 * Default is gray.
	 */
	SHADOWCOLOR: 'gray',

	/**
	 * Variable: SHADOW_OFFSET_X
	 * 
	 * Specifies the x-offset of the shadow. Default is 2.
	 */
	SHADOW_OFFSET_X: 2,

	/**
	 * Variable: SHADOW_OFFSET_Y
	 * 
	 * Specifies the y-offset of the shadow. Default is 3.
	 */
	SHADOW_OFFSET_Y: 3,
	
	/**
	 * Variable: SHADOW_OPACITY
	 * 
	 * Defines the opacity for shadows. Default is 1.
	 */
	SHADOW_OPACITY: 1,
 
	/**
	 * Variable: NODETYPE_ELEMENT
	 * 
	 * DOM node of type ELEMENT.
	 */
	NODETYPE_ELEMENT: 1,

	/**
	 * Variable: NODETYPE_ATTRIBUTE
	 * 
	 * DOM node of type ATTRIBUTE.
	 */
	NODETYPE_ATTRIBUTE: 2,

	/**
	 * Variable: NODETYPE_TEXT
	 * 
	 * DOM node of type TEXT.
	 */
	NODETYPE_TEXT: 3,

	/**
	 * Variable: NODETYPE_CDATA
	 * 
	 * DOM node of type CDATA.
	 */
	NODETYPE_CDATA: 4,
	
	/**
	 * Variable: NODETYPE_ENTITY_REFERENCE
	 * 
	 * DOM node of type ENTITY_REFERENCE.
	 */
	NODETYPE_ENTITY_REFERENCE: 5,

	/**
	 * Variable: NODETYPE_ENTITY
	 * 
	 * DOM node of type ENTITY.
	 */
	NODETYPE_ENTITY: 6,

	/**
	 * Variable: NODETYPE_PROCESSING_INSTRUCTION
	 * 
	 * DOM node of type PROCESSING_INSTRUCTION.
	 */
	NODETYPE_PROCESSING_INSTRUCTION: 7,

	/**
	 * Variable: NODETYPE_COMMENT
	 * 
	 * DOM node of type COMMENT.
	 */
	NODETYPE_COMMENT: 8,
		
	/**
	 * Variable: NODETYPE_DOCUMENT
	 * 
	 * DOM node of type DOCUMENT.
	 */
	NODETYPE_DOCUMENT: 9,

	/**
	 * Variable: NODETYPE_DOCUMENTTYPE
	 * 
	 * DOM node of type DOCUMENTTYPE.
	 */
	NODETYPE_DOCUMENTTYPE: 10,

	/**
	 * Variable: NODETYPE_DOCUMENT_FRAGMENT
	 * 
	 * DOM node of type DOCUMENT_FRAGMENT.
	 */
	NODETYPE_DOCUMENT_FRAGMENT: 11,

	/**
	 * Variable: NODETYPE_NOTATION
	 * 
	 * DOM node of type NOTATION.
	 */
	NODETYPE_NOTATION: 12,
	
	/**
	 * Variable: TOOLTIP_VERTICAL_OFFSET
	 * 
	 * Defines the vertical offset for the tooltip.
	 * Default is 16.
	 */
	TOOLTIP_VERTICAL_OFFSET: 16,

	/**
	 * Variable: DEFAULT_VALID_COLOR
	 * 
	 * Specifies the default valid colorr. Default is #0000FF.
	 */
	DEFAULT_VALID_COLOR: '#00FF00',

	/**
	 * Variable: DEFAULT_INVALID_COLOR
	 * 
	 * Specifies the default invalid color. Default is #FF0000.
	 */
	DEFAULT_INVALID_COLOR: '#FF0000',

	/**
	 * Variable: HIGHLIGHT_STROKEWIDTH
	 * 
	 * Defines the strokewidth to be used for the highlights.
	 * Default is 3.
	 */
	HIGHLIGHT_STROKEWIDTH: 3,
	
	/**
	 * Variable: CURSOR_MOVABLE_VERTEX
	 * 
	 * Defines the cursor for a movable vertex. Default is 'move'.
	 */
	CURSOR_MOVABLE_VERTEX: 'move',
	
	/**
	 * Variable: CURSOR_MOVABLE_EDGE
	 * 
	 * Defines the cursor for a movable edge. Default is 'move'.
	 */
	CURSOR_MOVABLE_EDGE: 'move',
	
	/**
	 * Variable: CURSOR_LABEL_HANDLE
	 * 
	 * Defines the cursor for a movable label. Default is 'default'.
	 */
	CURSOR_LABEL_HANDLE: 'default',
	
	/**
	 * Variable: CURSOR_BEND_HANDLE
	 * 
	 * Defines the cursor for a movable bend. Default is 'pointer'.
	 */
	CURSOR_BEND_HANDLE: 'pointer',
	
	/**
	 * Variable: CURSOR_CONNECT
	 * 
	 * Defines the cursor for a connectable state. Default is 'pointer'.
	 */
	CURSOR_CONNECT: 'pointer',

	/**
	 * Variable: HIGHLIGHT_COLOR
	 * 
	 * Defines the color to be used for the cell highlighting.
	 * Use 'none' for no color. Default is #00FF00.
	 */
	HIGHLIGHT_COLOR: '#00FF00',

	/**
	 * Variable: TARGET_HIGHLIGHT_COLOR
	 * 
	 * Defines the color to be used for highlighting a target cell for a new
	 * or changed connection. Note that this may be either a source or
	 * target terminal in the graph. Use 'none' for no color.
	 * Default is #0000FF.
	 */
	CONNECT_TARGET_COLOR: '#0000FF',

	/**
	 * Variable: INVALID_CONNECT_TARGET_COLOR
	 * 
	 * Defines the color to be used for highlighting a invalid target cells
	 * for a new or changed connections. Note that this may be either a source
	 * or target terminal in the graph. Use 'none' for no color. Default is
	 * #FF0000.
	 */
	INVALID_CONNECT_TARGET_COLOR: '#FF0000',

	/**
	 * Variable: DROP_TARGET_COLOR
	 * 
	 * Defines the color to be used for the highlighting target parent cells
	 * (for drag and drop). Use 'none' for no color. Default is #0000FF.
	 */
	DROP_TARGET_COLOR: '#0000FF',

	/**
	 * Variable: VALID_COLOR
	 * 
	 * Defines the color to be used for the coloring valid connection
	 * previews. Use 'none' for no color. Default is #FF0000.
	 */
	VALID_COLOR: '#00FF00',

	/**
	 * Variable: INVALID_COLOR
	 * 
	 * Defines the color to be used for the coloring invalid connection
	 * previews. Use 'none' for no color. Default is #FF0000.
	 */
	INVALID_COLOR: '#FF0000',

	/**
	 * Variable: EDGE_SELECTION_COLOR
	 * 
	 * Defines the color to be used for the selection border of edges. Use
	 * 'none' for no color. Default is #00FF00.
	 */
	EDGE_SELECTION_COLOR: '#00FF00',

	/**
	 * Variable: VERTEX_SELECTION_COLOR
	 * 
	 * Defines the color to be used for the selection border of vertices. Use
	 * 'none' for no color. Default is #00FF00.
	 */
	VERTEX_SELECTION_COLOR: '#00FF00',

	/**
	 * Variable: VERTEX_SELECTION_STROKEWIDTH
	 * 
	 * Defines the strokewidth to be used for vertex selections.
	 * Default is 1.
	 */
	VERTEX_SELECTION_STROKEWIDTH: 1,

	/**
	 * Variable: EDGE_SELECTION_STROKEWIDTH
	 * 
	 * Defines the strokewidth to be used for edge selections.
	 * Default is 1.
	 */
	EDGE_SELECTION_STROKEWIDTH: 1,

	/**
	 * Variable: SELECTION_DASHED
	 * 
	 * Defines the dashed state to be used for the vertex selection
	 * border. Default is true.
	 */
	VERTEX_SELECTION_DASHED: true,

	/**
	 * Variable: SELECTION_DASHED
	 * 
	 * Defines the dashed state to be used for the edge selection
	 * border. Default is true.
	 */
	EDGE_SELECTION_DASHED: true,

	/**
	 * Variable: GUIDE_COLOR
	 * 
	 * Defines the color to be used for the guidelines in mxGraphHandler.
	 * Default is #FF0000.
	 */
	GUIDE_COLOR: '#FF0000',

	/**
	 * Variable: GUIDE_STROKEWIDTH
	 * 
	 * Defines the strokewidth to be used for the guidelines in mxGraphHandler.
	 * Default is 1.
	 */
	GUIDE_STROKEWIDTH: 1,

	/**
	 * Variable: OUTLINE_COLOR
	 * 
	 * Defines the color to be used for the outline rectangle
	 * border.  Use 'none' for no color. Default is #0099FF.
	 */
	OUTLINE_COLOR: '#0099FF',

	/**
	 * Variable: OUTLINE_STROKEWIDTH
	 * 
	 * Defines the strokewidth to be used for the outline rectangle
	 * stroke width. Default is 3.
	 */
	OUTLINE_STROKEWIDTH: (mxClient.IS_IE) ? 2 : 3,

	/**
	 * Variable: HANDLE_SIZE
	 * 
	 * Defines the default size for handles. Default is 7.
	 */
	HANDLE_SIZE: 7,

	/**
	 * Variable: LABEL_HANDLE_SIZE
	 * 
	 * Defines the default size for label handles. Default is 4.
	 */
	LABEL_HANDLE_SIZE: 4,

	/**
	 * Variable: HANDLE_FILLCOLOR
	 * 
	 * Defines the color to be used for the handle fill color. Use 'none' for
	 * no color. Default is #00FF00 (green).
	 */
	HANDLE_FILLCOLOR: '#00FF00',

	/**
	 * Variable: HANDLE_STROKECOLOR
	 * 
	 * Defines the color to be used for the handle stroke color. Use 'none' for
	 * no color. Default is black.
	 */
	HANDLE_STROKECOLOR: 'black',

	/**
	 * Variable: LABEL_HANDLE_FILLCOLOR
	 * 
	 * Defines the color to be used for the label handle fill color. Use 'none'
	 * for no color. Default is yellow.
	 */
	LABEL_HANDLE_FILLCOLOR: 'yellow',

	/**
	 * Variable: CONNECT_HANDLE_FILLCOLOR
	 * 
	 * Defines the color to be used for the connect handle fill color. Use
	 * 'none' for no color. Default is #0000FF (blue).
	 */
	CONNECT_HANDLE_FILLCOLOR: '#0000FF',

	/**
	 * Variable: LOCKED_HANDLE_FILLCOLOR
	 * 
	 * Defines the color to be used for the locked handle fill color. Use
	 * 'none' for no color. Default is #FF0000 (red).
	 */
	LOCKED_HANDLE_FILLCOLOR: '#FF0000',

	/**
	 * Variable: OUTLINE_HANDLE_FILLCOLOR
	 * 
	 * Defines the color to be used for the outline sizer fill color. Use
	 * 'none' for no color. Default is #00FFFF.
	 */
	OUTLINE_HANDLE_FILLCOLOR: '#00FFFF',

	/**
	 * Variable: OUTLINE_HANDLE_STROKECOLOR
	 * 
	 * Defines the color to be used for the outline sizer stroke color. Use
	 * 'none' for no color. Default is #0033FF.
	 */
	OUTLINE_HANDLE_STROKECOLOR: '#0033FF',

	/**
	 * Variable: DEFAULT_FONTFAMILY
	 * 
	 * Defines the default family for all fonts in points. Default is
	 * Arial,Helvetica.
	 */
	DEFAULT_FONTFAMILY: 'Arial,Helvetica',

	/**
	 * Variable: DEFAULT_FONTSIZE
	 * 
	 * Defines the default size for all fonts in points. Default is 11.
	 */
	DEFAULT_FONTSIZE: 11,

	/**
	 * Variable: DEFAULT_STARTSIZE
	 * 
	 * Defines the default start size for swimlanes. Default is 40.
	 */
	DEFAULT_STARTSIZE: 40,

	/**
	 * Variable: DEFAULT_MARKERSIZE
	 * 
	 * Defines the default size for all markers. Default is 6.
	 */
	DEFAULT_MARKERSIZE: 6,

	/**
	 * Variable: DEFAULT_IMAGESIZE
	 * 
	 * Defines the default width and height for images used in the
	 * label shape. Default is 24.
	 */
	DEFAULT_IMAGESIZE: 24,

	/**
	 * Variable: ENTITY_SEGMENT
	 * 
	 * Defines the length of the horizontal segment of an Entity Relation.
	 * This can be overridden using  style.
	 * Default is 30.
	 */
	ENTITY_SEGMENT: 30,

	/**
	 * Variable: RECTANGLE_ROUNDING_FACTOR
	 * 
	 * Defines the rounding factor for rounded rectangles in percent between
	 * 0 and 1. Values should be smaller than 0.5. Default is 0.15.
	 */
	RECTANGLE_ROUNDING_FACTOR: 0.15,

	/**
	 * Variable: LINE_ARCSIZE
	 * 
	 * Defines the size of the arcs for rounded edges. Default is 20.
	 */
	LINE_ARCSIZE: 20,

	/**
	 * Variable: ARROW_SPACING
	 * 
	 * Defines the spacing between the arrow shape and its terminals. Default
	 * is 10.
	 */
	ARROW_SPACING: 10,

	/**
	 * Variable: ARROW_WIDTH
	 * 
	 * Defines the width of the arrow shape. Default is 30.
	 */
	ARROW_WIDTH: 30,

	/**
	 * Variable: ARROW_SIZE
	 * 
	 * Defines the size of the arrowhead in the arrow shape. Default is 30.
	 */
	ARROW_SIZE: 30,

	/**
	 * Variable: PAGE_FORMAT_A4_PORTRAIT
	 * 
	 * Defines the rectangle for the A4 portrait page format. The dimensions
	 * of this page format are 826x1169 pixels.
	 */
	PAGE_FORMAT_A4_PORTRAIT: new mxRectangle(0, 0, 826, 1169),

	/**
	 * Variable: PAGE_FORMAT_A4_PORTRAIT
	 * 
	 * Defines the rectangle for the A4 portrait page format. The dimensions
	 * of this page format are 826x1169 pixels.
	 */
	PAGE_FORMAT_A4_LANDSCAPE: new mxRectangle(0, 0, 1169, 826),

	/**
	 * Variable: PAGE_FORMAT_LETTER_PORTRAIT
	 * 
	 * Defines the rectangle for the Letter portrait page format. The
	 * dimensions of this page format are 850x1100 pixels.
	 */
	PAGE_FORMAT_LETTER_PORTRAIT: new mxRectangle(0, 0, 850, 1100),

	/**
	 * Variable: PAGE_FORMAT_LETTER_PORTRAIT
	 * 
	 * Defines the rectangle for the Letter portrait page format. The dimensions
	 * of this page format are 850x1100 pixels.
	 */
	PAGE_FORMAT_LETTER_LANDSCAPE: new mxRectangle(0, 0, 1100, 850),

	/**
	 * Variable: NONE
	 * 
	 * Defines the value for none. Default is "none".
	 */
	NONE: 'none',

	/**
	 * Variable: STYLE_PERIMETER
	 * 
	 * Defines the key for the perimeter style. This is a function that defines
	 * the perimeter around a particular shape. Possible values are the
	 * functions defined in . Alternatively, the constants in this
	 * class that start with PERIMETER_ may be used to access
	 * perimeter styles in .
	 */
	STYLE_PERIMETER: 'perimeter',
	
	/**
	 * Variable: STYLE_SOURCE_PORT
	 * 
	 * Defines the ID of the cell that should be used for computing the
	 * perimeter point of the source for an edge. This allows for graphically
	 * connecting to a cell while keeping the actual terminal of the edge.
	 */
	STYLE_SOURCE_PORT: 'sourcePort',
	
	/**
	 * Variable: STYLE_TARGET_PORT
	 * 
	 * Defines the ID of the cell that should be used for computing the
	 * perimeter point of the target for an edge. This allows for graphically
	 * connecting to a cell while keeping the actual terminal of the edge.
	 */
	STYLE_TARGET_PORT: 'targetPort',

	/**
	 * Variable: STYLE_PORT_CONSTRAINT
	 * 
	 * Defines the direction(s) that edges are allowed to connect to cells in.
	 * Possible values are DIRECTION_NORTH, DIRECTION_SOUTH, 
	 * DIRECTION_EAST and DIRECTION_WEST.
	 */
	STYLE_PORT_CONSTRAINT: 'portConstraint',

	/**
	 * Variable: STYLE_OPACITY
	 * 
	 * Defines the key for the opacity style. The type of the value is 
	 * numeric and the possible range is 0-100.
	 */
	STYLE_OPACITY: 'opacity',

	/**
	 * Variable: STYLE_TEXT_OPACITY
	 * 
	 * Defines the key for the text opacity style. The type of the value is 
	 * numeric and the possible range is 0-100.
	 */
	STYLE_TEXT_OPACITY: 'textOpacity',

	/**
	 * Variable: STYLE_OVERFLOW
	 * 
	 * Defines the key for the overflow style. Possible values are 'visible',
	 * 'hidden' and 'fill'. The default value is 'visible'. This value
	 * specifies how overlapping vertex labels are handled. A value of
	 * 'visible' will show the complete label. A value of 'hidden' will clip
	 * the label so that it does not overlap the vertex bounds. A value of
	 * 'fill' will use the vertex bounds for the label. See
	 * .
	 */
	STYLE_OVERFLOW: 'overflow',

	/**
	 * Variable: STYLE_ORTHOGONAL
	 * 
	 * Defines if the connection points on either end of the edge should be
	 * computed so that the edge is vertical or horizontal if possible and
	 * if the point is not at a fixed location. Default is false. This is
	 * used in , which also returns true if the edgeStyle
	 * of the edge is an elbow or entity.
	 */
	STYLE_ORTHOGONAL: 'orthogonal',

	/**
	 * Variable: STYLE_EXIT_X
	 * 
	 * Defines the key for the horizontal relative coordinate connection point
	 * of an edge with its source terminal.
	 */
	STYLE_EXIT_X: 'exitX',

	/**
	 * Variable: STYLE_EXIT_Y
	 * 
	 * Defines the key for the vertical relative coordinate connection point
	 * of an edge with its source terminal.
	 */
	STYLE_EXIT_Y: 'exitY',

	/**
	 * Variable: STYLE_EXIT_PERIMETER
	 * 
	 * Defines if the perimeter should be used to find the exact entry point
	 * along the perimeter of the source. Possible values are 0 (false) and
	 * 1 (true). Default is 1 (true).
	 */
	STYLE_EXIT_PERIMETER: 'exitPerimeter',

	/**
	 * Variable: STYLE_ENTRY_X
	 * 
	 * Defines the key for the horizontal relative coordinate connection point
	 * of an edge with its target terminal.
	 */
	STYLE_ENTRY_X: 'entryX',

	/**
	 * Variable: STYLE_ENTRY_Y
	 * 
	 * Defines the key for the vertical relative coordinate connection point
	 * of an edge with its target terminal.
	 */
	STYLE_ENTRY_Y: 'entryY',

	/**
	 * Variable: STYLE_ENTRY_PERIMETER
	 * 
	 * Defines if the perimeter should be used to find the exact entry point
	 * along the perimeter of the target. Possible values are 0 (false) and
	 * 1 (true). Default is 1 (true).
	 */
	STYLE_ENTRY_PERIMETER: 'entryPerimeter',

	/**
	 * Variable: STYLE_WHITE_SPACE
	 * 
	 * Defines the key for the white-space style. Possible values are 'nowrap'
	 * and 'wrap'. The default value is 'nowrap'. This value specifies how
	 * white-space inside a HTML vertex label should be handled. A value of
	 * 'nowrap' means the text will never wrap to the next line until a
	 * linefeed is encountered. A value of 'wrap' means text will wrap when
	 * necessary. This style is only used for HTML labels.
	 * See .
	 */
	STYLE_WHITE_SPACE: 'whiteSpace',

	/**
	 * Variable: STYLE_ROTATION
	 * 
	 * Defines the key for the rotation style. The type of the value is 
	 * numeric and the possible range is 0-360.
	 */
	STYLE_ROTATION: 'rotation',

	/**
	 * Variable: STYLE_FILLCOLOR
	 * 
	 * Defines the key for the fill color. Possible values are all HTML color
	 * names or HEX codes, as well as special keywords such as 'swimlane,
	 * 'inherit' or 'indicated' to use the color code of a related cell or the
	 * indicator shape.
	 */
	STYLE_FILLCOLOR: 'fillColor',

	/**
	 * Variable: STYLE_GRADIENTCOLOR
	 * 
	 * Defines the key for the gradient color. Possible values are all HTML color
	 * names or HEX codes, as well as special keywords such as 'swimlane,
	 * 'inherit' or 'indicated' to use the color code of a related cell or the
	 * indicator shape. This is ignored if no fill color is defined.
	 */
	STYLE_GRADIENTCOLOR: 'gradientColor',

	/**
	 * Variable: STYLE_GRADIENT_DIRECTION
	 * 
	 * Defines the key for the gradient direction. Possible values are
	 * , ,  and
	 * . Default is . Generally, and by
	 * default in mxGraph, gradient painting is done from the value of
	 *  to the value of . Taking the
	 * example of , this means  color at the 
	 * bottom of paint pattern and  at top, with a
	 * gradient in-between.
	 */
	STYLE_GRADIENT_DIRECTION: 'gradientDirection',

	/**
	 * Variable: STYLE_STROKECOLOR
	 * 
	 * Defines the key for the strokeColor style. Possible values are all HTML
	 * color names or HEX codes, as well as special keywords such as 'swimlane,
	 * 'inherit', 'indicated' to use the color code of a related cell or the
	 * indicator shape or 'none' for no color.
	 */
	STYLE_STROKECOLOR: 'strokeColor',

	/**
	 * Variable: STYLE_SEPARATORCOLOR
	 * 
	 * Defines the key for the separatorColor style. Possible values are all
	 * HTML color names or HEX codes. This style is only used for
	 *  shapes.
	 */
	STYLE_SEPARATORCOLOR: 'separatorColor',

	/**
	 * Variable: STYLE_STROKEWIDTH
	 * 
	 * Defines the key for the strokeWidth style. The type of the value is 
	 * numeric and the possible range is any non-negative value larger or equal
	 * to 1. The value defines the stroke width in pixels. Note: To hide a
	 * stroke use strokeColor none.
	 */
	STYLE_STROKEWIDTH: 'strokeWidth',

	/**
	 * Variable: STYLE_ALIGN
	 * 
	 * Defines the key for the align style. Possible values are ,
	 *  and . This value defines how the lines of
	 * the label are horizontally aligned.  mean label text lines
	 * are aligned to left of the label bounds,  to the right of
	 * the label bounds and  means the center of the text lines
	 * are aligned in the center of the label bounds. Note this value doesn't
	 * affect the positioning of the overall label bounds relative to the
	 * vertex, to move the label bounds horizontally, use
	 * .
	 */
	STYLE_ALIGN: 'align',

	/**
	 * Variable: STYLE_VERTICAL_ALIGN
	 * 
	 * Defines the key for the verticalAlign style. Possible values are
	 * ,  and . This value defines how
	 * the lines of the label are vertically aligned.  means the
	 * topmost label text line is aligned against the top of the label bounds,
	 *  means the bottom-most label text line is aligned against
	 * the bottom of the label bounds and  means there is equal
	 * spacing between the topmost text label line and the top of the label
	 * bounds and the bottom-most text label line and the bottom of the label
	 * bounds. Note this value doesn't affect the positioning of the overall
	 * label bounds relative to the vertex, to move the label bounds
	 * vertically, use .
	 */
	STYLE_VERTICAL_ALIGN: 'verticalAlign',

	/**
	 * Variable: STYLE_LABEL_POSITION
	 * 
	 * Defines the key for the horizontal label position of vertices. Possible
	 * values are ,  and . Default is
	 * . The label align defines the position of the label
	 * relative to the cell.  means the entire label bounds is
	 * placed completely just to the left of the vertex,  means
	 * adjust to the right and  means the label bounds are
	 * vertically aligned with the bounds of the vertex. Note this value
	 * doesn't affect the positioning of label within the label bounds, to move
	 * the label horizontally within the label bounds, use .
	 */
	STYLE_LABEL_POSITION: 'labelPosition',

	/**
	 * Variable: STYLE_VERTICAL_LABEL_POSITION
	 * 
	 * Defines the key for the vertical label position of vertices. Possible
	 * values are ,  and . Default is
	 * . The label align defines the position of the label
	 * relative to the cell.  means the entire label bounds is
	 * placed completely just on the top of the vertex,  means
	 * adjust on the bottom and  means the label bounds are
	 * horizontally aligned with the bounds of the vertex. Note this value
	 * doesn't affect the positioning of label within the label bounds, to move
	 * the label vertically within the label bounds, use
	 * .
	 */
	STYLE_VERTICAL_LABEL_POSITION: 'verticalLabelPosition',
	
	/**
	 * Variable: STYLE_IMAGE_ASPECT
	 * 
	 * Defines the key for the image aspect style. Possible values are 0 (do
	 * not preserve aspect) or 1 (keep aspect). This is only used in
	 * . Default is 1.
	 */
	STYLE_IMAGE_ASPECT: 'imageAspect',

	/**
	 * Variable: STYLE_IMAGE_ALIGN
	 * 
	 * Defines the key for the align style. Possible values are ,
	 *  and . The value defines how any image in the
	 * vertex label is aligned horizontally within the label bounds of a
	 *  shape.
	 */
	STYLE_IMAGE_ALIGN: 'imageAlign',

	/**
	 * Variable: STYLE_IMAGE_VERTICAL_ALIGN
	 * 
	 * Defines the key for the verticalAlign style. Possible values are
	 * ,  and . The value defines how
	 * any image in the vertex label is aligned vertically within the label
	 * bounds of a  shape.
	 */
	STYLE_IMAGE_VERTICAL_ALIGN: 'imageVerticalAlign',

	/**
	 * Variable: STYLE_GLASS
	 * 
	 * Defines the key for the glass style. Possible values are 0 (disabled) and
	 * 1(enabled). The default value is 0. This is used in .
	 */
	STYLE_GLASS: 'glass',

	/**
	 * Variable: STYLE_IMAGE
	 * 
	 * Defines the key for the image style. Possible values are any image URL,
	 * the type of the value is String. This is the path to the image to image
	 * that is to be displayed within the label of a vertex. Data URLs should
	 * use the following format: data:image/png,xyz where xyz is the base64
	 * encoded data (without the "base64"-prefix). Note that Data URLs are only
	 * supported in modern browsers.
	 */
	STYLE_IMAGE: 'image',

	/**
	 * Variable: STYLE_IMAGE_WIDTH
	 * 
	 * Defines the key for the imageWidth style. The type of this value is
	 * int, the value is the image width in pixels and must be greater than 0.
	 */
	STYLE_IMAGE_WIDTH: 'imageWidth',

	/**
	 * Variable: STYLE_IMAGE_HEIGHT
	 * 
	 * Defines the key for the imageHeight style. The type of this value is
	 * int, the value is the image height in pixels and must be greater than 0.
	 */
	STYLE_IMAGE_HEIGHT: 'imageHeight',

	/**
	 * Variable: STYLE_IMAGE_BACKGROUND
	 * 
	 * Defines the key for the image background color. This style is only used
	 * in . Possible values are all HTML color names or HEX
	 * codes.
	 */
	STYLE_IMAGE_BACKGROUND: 'imageBackground',

	/**
	 * Variable: STYLE_IMAGE_BORDER
	 * 
	 * Defines the key for the image border color. This style is only used in
	 * . Possible values are all HTML color names or HEX codes.
	 */
	STYLE_IMAGE_BORDER: 'imageBorder',

	/**
	 * Variable: STYLE_IMAGE_FLIPH
	 * 
	 * Defines the key for the horizontal image flip. This style is only used
	 * in . Possible values are 0 and 1. Default is 0.
	 */
	STYLE_IMAGE_FLIPH: 'imageFlipH',

	/**
	 * Variable: STYLE_IMAGE_FLIPV
	 * 
	 * Defines the key for the vertical image flip. This style is only used
	 * in . Possible values are 0 and 1. Default is 0.
	 */
	STYLE_IMAGE_FLIPV: 'imageFlipV',

	/**
	 * Variable: STYLE_STENCIL_FLIPH
	 * 
	 * Defines the key for the horizontal stencil flip. This style is only used
	 * for . Possible values are 0 and 1. Default is 0.
	 */
	STYLE_STENCIL_FLIPH: 'stencilFlipH',

	/**
	 * Variable: STYLE_STENCIL_FLIPV
	 * 
	 * Defines the key for the vertical stencil flip. This style is only used
	 * for . Possible values are 0 and 1. Default is 0.
	 */
	STYLE_STENCIL_FLIPV: 'stencilFlipV',

	/**
	 * Variable: STYLE_NOLABEL
	 * 
	 * Defines the key for the noLabel style. If this is
	 * true then no label is visible for a given cell.
	 * Possible values are true or false (1 or 0).
	 * Default is false.
	 */
	STYLE_NOLABEL: 'noLabel',

	/**
	 * Variable: STYLE_NOEDGESTYLE
	 * 
	 * Defines the key for the noEdgeStyle style. If this is
	 * true then no edge style is applied for a given edge.
	 * Possible values are true or false (1 or 0).
	 * Default is false.
	 */
	STYLE_NOEDGESTYLE: 'noEdgeStyle',

	/**
	 * Variable: STYLE_LABEL_BACKGROUNDCOLOR
	 * 
	 * Defines the key for the label background color. Possible values are all
	 * HTML color names or HEX codes.
	 */
	STYLE_LABEL_BACKGROUNDCOLOR: 'labelBackgroundColor',

	/**
	 * Variable: STYLE_LABEL_BORDERCOLOR
	 * 
	 * Defines the key for the label border color. Possible values are all
	 * HTML color names or HEX codes.
	 */
	STYLE_LABEL_BORDERCOLOR: 'labelBorderColor',

	/**
	 * Variable: STYLE_LABEL_PADDING
	 * 
	 * Defines the key for the label padding, ie. the space between the label
	 * border and the label.
	 */
	STYLE_LABEL_PADDING: 'labelPadding',

	/**
	 * Variable: STYLE_INDICATOR_SHAPE
	 * 
	 * Defines the key for the indicator shape used within an .
	 * Possible values are all SHAPE_* constants or the names of any new
	 * shapes. The indicatorShape has precedence over the indicatorImage.
	 */
	STYLE_INDICATOR_SHAPE: 'indicatorShape',

	/**
	 * Variable: STYLE_INDICATOR_IMAGE
	 * 
	 * Defines the key for the indicator image used within an .
	 * Possible values are all image URLs. The indicatorShape has
	 * precedence over the indicatorImage.
	 */
	STYLE_INDICATOR_IMAGE: 'indicatorImage',

	/**
	 * Variable: STYLE_INDICATOR_COLOR
	 * 
	 * Defines the key for the indicatorColor style. Possible values are all
	 * HTML color names or HEX codes, as well as the special 'swimlane' keyword
	 * to refer to the color of the parent swimlane if one exists.
	 */
	STYLE_INDICATOR_COLOR: 'indicatorColor',

	/**
	 * Variable: STYLE_INDICATOR_STROKECOLOR
	 * 
	 * Defines the key for the indicator stroke color in .
	 * Possible values are all color codes.
	 */
	STYLE_INDICATOR_STROKECOLOR: 'indicatorStrokeColor',

	/**
	 * Variable: STYLE_INDICATOR_GRADIENTCOLOR
	 * 
	 * Defines the key for the indicatorGradientColor style. Possible values
	 * are all HTML color names or HEX codes. This style is only supported in
	 *  shapes.
	 */
	STYLE_INDICATOR_GRADIENTCOLOR: 'indicatorGradientColor',

	/**
	 * Variable: STYLE_INDICATOR_SPACING
	 * 
	 * The defines the key for the spacing between the label and the
	 * indicator in . Possible values are in pixels.
	 */
	STYLE_INDICATOR_SPACING: 'indicatorSpacing',

	/**
	 * Variable: STYLE_INDICATOR_WIDTH
	 * 
	 * Defines the key for the indicator width.
	 * Possible values start at 0 (in pixels).
	 */
	STYLE_INDICATOR_WIDTH: 'indicatorWidth',

	/**
	 * Variable: STYLE_INDICATOR_HEIGHT
	 * 
	 * Defines the key for the indicator height.
	 * Possible values start at 0 (in pixels).
	 */
	STYLE_INDICATOR_HEIGHT: 'indicatorHeight',

	/**
	 * Variable: STYLE_INDICATOR_DIRECTION
	 * 
	 * Defines the key for the indicatorDirection style. The direction style is
	 * used to specify the direction of certain shapes (eg. ).
	 * Possible values are  (default), ,
	 *  and .
	 */
	STYLE_INDICATOR_DIRECTION: 'indicatorDirection',

	/**
	 * Variable: STYLE_SHADOW
	 * 
	 * Defines the key for the shadow style. The type of the value is Boolean.
	 */
	STYLE_SHADOW: 'shadow',
	
	/**
	 * Variable: STYLE_SEGMENT
	 * 
	 * Defines the key for the segment style. The type of this value is
	 * float and the value represents the size of the horizontal
	 * segment of the entity relation style. Default is ENTITY_SEGMENT.
	 */
	STYLE_SEGMENT: 'segment',
	
	/**
	 * Variable: STYLE_ENDARROW
	 *
	 * Defines the key for the end arrow marker.
	 * Possible values are all constants with an ARROW-prefix.
	 * This is only used in .
	 *
	 * Example:
	 * (code)
	 * style[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC;
	 * (end)
	 */
	STYLE_ENDARROW: 'endArrow',

	/**
	 * Variable: STYLE_STARTARROW
	 * 
	 * Defines the key for the start arrow marker.
	 * Possible values are all constants with an ARROW-prefix.
	 * This is only used in .
	 * See .
	 */
	STYLE_STARTARROW: 'startArrow',

	/**
	 * Variable: STYLE_ENDSIZE
	 * 
	 * Defines the key for the endSize style. The type of this value is numeric
	 * and the value represents the size of the end marker in pixels.
	 */
	STYLE_ENDSIZE: 'endSize',

	/**
	 * Variable: STYLE_STARTSIZE
	 * 
	 * Defines the key for the startSize style. The type of this value is
	 * numeric and the value represents the size of the start marker or the
	 * size of the swimlane title region depending on the shape it is used for.
	 */
	STYLE_STARTSIZE: 'startSize',

	/**
	 * Variable: STYLE_ENDFILL
	 * 
	 * Defines the key for the endFill style. Use 0 for no fill or 1
	 * (default) for fill. (This style is only exported via .)
	 */
	STYLE_ENDFILL: 'endFill',

	/**
	 * Variable: STYLE_STARTFILL
	 * 
	 * Defines the key for the startFill style. Use 0 for no fill or 1
	 * (default) for fill. (This style is only exported via .)
	 */
	STYLE_STARTFILL: 'startFill',

	/**
	 * Variable: STYLE_DASHED
	 * 
	 * Defines the key for the dashed style. Use 0 (default) for non-dashed or 1
	 * for dashed.
	 */
	STYLE_DASHED: 'dashed',

	/**
	 * Defines the key for the dashed pattern style in SVG and image exports.
	 * The type of this value is a space separated list of numbers that specify
	 * a custom-defined dash pattern. Dash styles are defined in terms of the
	 * length of the dash (the drawn part of the stroke) and the length of the
	 * space between the dashes. The lengths are relative to the line width: a
	 * length of "1" is equal to the line width. VML ignores this style and
	 * uses dashStyle instead as defined in the VML specification. This style
	 * is only used in the  shape.
	 */
	STYLE_DASH_PATTERN: 'dashPattern',

	/**
	 * Variable: STYLE_ROUNDED
	 * 
	 * Defines the key for the rounded style. The type of this value is
	 * Boolean. For edges this determines whether or not joins between edges
	 * segments are smoothed to a rounded finish. For vertices that have the
	 * rectangle shape, this determines whether or not the rectangle is
	 * rounded.
	 */
	STYLE_ROUNDED: 'rounded',

	/**
	 * Variable: STYLE_CURVED
	 * 
	 * Defines the key for the curved style. The type of this value is
	 * Boolean. It is only applicable for connector shapes. Use 0 (default)
	 * for non-curved or 1 for curved.
	 */
	STYLE_CURVED: 'curved',

	/**
	 * Variable: STYLE_ARCSIZE
	 * 
	 * Defines the rounding factor for a rounded rectangle in percent (without
	 * the percent sign). Possible values are between 0 and 100. If this value
	 * is not specified then RECTANGLE_ROUNDING_FACTOR * 100 is used.
	 * (This style is only exported via .)
	 */
	STYLE_ARCSIZE: 'arcSize',

	/**
	 * Variable: STYLE_SMOOTH
	 * 
	 * An experimental style for edges. This style is currently not available
	 * in the backends and is implemented differently for VML and SVG. The use
	 * of this style is currently only recommended for VML.
	 */
	STYLE_SMOOTH: 'smooth',

	/**
	 * Variable: STYLE_SOURCE_PERIMETER_SPACING
	 * 
	 * Defines the key for the source perimeter spacing. The type of this value
	 * is numeric. This is the distance between the source connection point of
	 * an edge and the perimeter of the source vertex in pixels. This style
	 * only applies to edges.
	 */
	STYLE_SOURCE_PERIMETER_SPACING: 'sourcePerimeterSpacing',

	/**
	 * Variable: STYLE_TARGET_PERIMETER_SPACING
	 * 
	 * Defines the key for the target perimeter spacing. The type of this value
	 * is numeric. This is the distance between the target connection point of
	 * an edge and the perimeter of the target vertex in pixels. This style
	 * only applies to edges.
	 */
	STYLE_TARGET_PERIMETER_SPACING: 'targetPerimeterSpacing',

	/**
	 * Variable: STYLE_PERIMETER_SPACING
	 * 
	 * Defines the key for the perimeter spacing. This is the distance between
	 * the connection point and the perimeter in pixels. When used in a vertex
	 * style, this applies to all incoming edges to floating ports (edges that
	 * terminate on the perimeter of the vertex). When used in an edge style,
	 * this spacing applies to the source and target separately, if they
	 * terminate in floating ports (on the perimeter of the vertex).
	 */
	STYLE_PERIMETER_SPACING: 'perimeterSpacing',

	/**
	 * Variable: STYLE_SPACING
	 * 
	 * Defines the key for the spacing. The value represents the spacing, in
	 * pixels, added to each side of a label in a vertex (style applies to
	 * vertices only).
	 */
	STYLE_SPACING: 'spacing',

	/**
	 * Variable: STYLE_SPACING_TOP
	 * 
	 * Defines the key for the spacingTop style. The value represents the
	 * spacing, in pixels, added to the top side of a label in a vertex (style
	 * applies to vertices only).
	 */
	STYLE_SPACING_TOP: 'spacingTop',

	/**
	 * Variable: STYLE_SPACING_LEFT
	 * 
	 * Defines the key for the spacingLeft style. The value represents the
	 * spacing, in pixels, added to the left side of a label in a vertex (style
	 * applies to vertices only).
	 */
	STYLE_SPACING_LEFT: 'spacingLeft',

	/**
	 * Variable: STYLE_SPACING_BOTTOM
	 * 
	 * Defines the key for the spacingBottom style The value represents the
	 * spacing, in pixels, added to the bottom side of a label in a vertex
	 * (style applies to vertices only).
	 */
	STYLE_SPACING_BOTTOM: 'spacingBottom',

	/**
	 * Variable: STYLE_SPACING_RIGHT
	 * 
	 * Defines the key for the spacingRight style The value represents the
	 * spacing, in pixels, added to the right side of a label in a vertex (style
	 * applies to vertices only).
	 */
	STYLE_SPACING_RIGHT: 'spacingRight',

	/**
	 * Variable: STYLE_HORIZONTAL
	 * 
	 * Defines the key for the horizontal style. Possible values are
	 * true or false. This value only applies to vertices. If the 
	 * is SHAPE_SWIMLANE a value of false indicates that the
	 * swimlane should be drawn vertically, true indicates to draw it
	 * horizontally. If the shape style does not indicate that this vertex is a
	 * swimlane, this value affects only whether the label is drawn
	 * horizontally or vertically.
	 */
	STYLE_HORIZONTAL: 'horizontal',

	/**
	 * Variable: STYLE_DIRECTION
	 * 
	 * Defines the key for the direction style. The direction style is used
	 * to specify the direction of certain shapes (eg. ).
	 * Possible values are  (default), ,
	 *  and .
	 */
	STYLE_DIRECTION: 'direction',

	/**
	 * Variable: STYLE_ELBOW
	 * 
	 * Defines the key for the elbow style. Possible values are
	 *  and . Default is .
	 * This defines how the three segment orthogonal edge style leaves its
	 * terminal vertices. The vertical style leaves the terminal vertices at
	 * the top and bottom sides.
	 */
	STYLE_ELBOW: 'elbow',

	/**
	 * Variable: STYLE_FONTCOLOR
	 * 
	 * Defines the key for the fontColor style. Possible values are all HTML
	 * color names or HEX codes.
	 */
	STYLE_FONTCOLOR: 'fontColor',

	/**
	 * Variable: STYLE_FONTFAMILY
	 * 
	 * Defines the key for the fontFamily style. Possible values are names such
	 * as Arial; Dialog; Verdana; Times New Roman. The value is of type String.
	 */
	STYLE_FONTFAMILY: 'fontFamily',

	/**
	 * Variable: STYLE_FONTSIZE
	 * 
	 * Defines the key for the fontSize style (in points). The type of the value
	 * is int.
	 */
	STYLE_FONTSIZE: 'fontSize',

	/**
	 * Variable: STYLE_FONTSTYLE
	 * 
	 * Defines the key for the fontStyle style. Values may be any logical AND
	 * (sum) of , ,  and .
	 * The type of the value is int.
	 */
	STYLE_FONTSTYLE: 'fontStyle',

	/**
	 * Variable: STYLE_AUTOSIZE
	 * 
	 * Defines the key for the autosize style. This specifies if a cell should be
	 * resized automatically if the value has changed. Possible values are 0 or 1.
	 * Default is 0. See . This is normally combined with
	 *  to disable manual sizing.
	 */
	STYLE_AUTOSIZE: 'autosize',

	/**
	 * Variable: STYLE_FOLDABLE
	 * 
	 * Defines the key for the foldable style. This specifies if a cell is foldable
	 * using a folding icon. Possible values are 0 or 1. Default is 1. See
	 * .
	 */
	STYLE_FOLDABLE: 'foldable',

	/**
	 * Variable: STYLE_EDITABLE
	 * 
	 * Defines the key for the editable style. This specifies if the value of
	 * a cell can be edited using the in-place editor. Possible values are 0 or
	 * 1. Default is 1. See .
	 */
	STYLE_EDITABLE: 'editable',

	/**
	 * Variable: STYLE_BENDABLE
	 * 
	 * Defines the key for the bendable style. This specifies if the control
	 * points of an edge can be moved. Possible values are 0 or 1. Default is
	 * 1. See .
	 */
	STYLE_BENDABLE: 'bendable',

	/**
	 * Variable: STYLE_MOVABLE
	 * 
	 * Defines the key for the movable style. This specifies if a cell can
	 * be moved. Possible values are 0 or 1. Default is 1. See
	 * .
	 */
	STYLE_MOVABLE: 'movable',

	/**
	 * Variable: STYLE_RESIZABLE
	 * 
	 * Defines the key for the resizable style. This specifies if a cell can
	 * be resized. Possible values are 0 or 1. Default is 1. See
	 * .
	 */
	STYLE_RESIZABLE: 'resizable',

	/**
	 * Variable: STYLE_CLONEABLE
	 * 
	 * Defines the key for the cloneable style. This specifies if a cell can
	 * be cloned. Possible values are 0 or 1. Default is 1. See
	 * .
	 */
	STYLE_CLONEABLE: 'cloneable',

	/**
	 * Variable: STYLE_DELETABLE
	 * 
	 * Defines the key for the deletable style. This specifies if a cell can be
	 * deleted. Possible values are 0 or 1. Default is 1. See
	 * .
	 */
	STYLE_DELETABLE: 'deletable',

	/**
	 * Variable: STYLE_SHAPE
	 * 
	 * Defines the key for the shape. Possible values are all constants
	 * with a SHAPE-prefix or any newly defined shape names.
	 */
	STYLE_SHAPE: 'shape',

	/**
	 * Variable: STYLE_EDGE
	 * 
	 * Defines the key for the edge style. Possible values are the functions
	 * defined in .
	 */
	STYLE_EDGE: 'edgeStyle',

	/**
	 * Variable: STYLE_LOOP
	 * 
	 * Defines the key for the loop style. Possible values are the functions
	 * defined in .
	 */
	STYLE_LOOP: 'loopStyle',

	/**
	 * Variable: STYLE_ROUTING_CENTER_X
	 * 
	 * Defines the key for the horizontal routing center. Possible values are
	 * between -0.5 and 0.5. This is the relative offset from the center used
	 * for connecting edges. The type of this value is numeric.
	 */
	STYLE_ROUTING_CENTER_X: 'routingCenterX',

	/**
	 * Variable: STYLE_ROUTING_CENTER_Y
	 * 
	 * Defines the key for the vertical routing center. Possible values are
	 * between -0.5 and 0.5. This is the relative offset from the center used
	 * for connecting edges. The type of this value is numeric.
	 */
	STYLE_ROUTING_CENTER_Y: 'routingCenterY',

	/**
	 * Variable: FONT_BOLD
	 * 
	 * Constant for bold fonts. Default is 1.
	 */
	FONT_BOLD: 1,

	/**
	 * Variable: FONT_ITALIC
	 * 
	 * Constant for italic fonts. Default is 2.
	 */
	FONT_ITALIC: 2,

	/**
	 * Variable: FONT_UNDERLINE
	 * 
	 * Constant for underlined fonts. Default is 4.
	 */
	FONT_UNDERLINE: 4,

	/**
	 * Variable: FONT_SHADOW
	 * 
	 * Constant for fonts with a shadow. Default is 8.
	 */
	FONT_SHADOW: 8,

	/**
	 * Variable: SHAPE_RECTANGLE
	 * 
	 * Name under which  is registered
	 * in . Default is rectangle.
	 */
	SHAPE_RECTANGLE: 'rectangle',

	/**
	 * Variable: SHAPE_ELLIPSE
	 * 
	 * Name under which  is registered
	 * in . Default is ellipse.
	 */
	SHAPE_ELLIPSE: 'ellipse',

	/**
	 * Variable: SHAPE_DOUBLE_ELLIPSE
	 * 
	 * Name under which  is registered
	 * in . Default is doubleEllipse.
	 */
	SHAPE_DOUBLE_ELLIPSE: 'doubleEllipse',

	/**
	 * Variable: SHAPE_RHOMBUS
	 * 
	 * Name under which  is registered
	 * in . Default is rhombus.
	 */
	SHAPE_RHOMBUS: 'rhombus',

	/**
	 * Variable: SHAPE_LINE
	 * 
	 * Name under which  is registered
	 * in . Default is line.
	 */
	SHAPE_LINE: 'line',

	/**
	 * Variable: SHAPE_IMAGE
	 * 
	 * Name under which  is registered
	 * in . Default is image.
	 */
	SHAPE_IMAGE: 'image',
	
	/**
	 * Variable: SHAPE_ARROW
	 * 
	 * Name under which  is registered
	 * in . Default is arrow.
	 */
	SHAPE_ARROW: 'arrow',
	
	/**
	 * Variable: SHAPE_LABEL
	 * 
	 * Name under which  is registered
	 * in . Default is label.
	 */
	SHAPE_LABEL: 'label',
	
	/**
	 * Variable: SHAPE_CYLINDER
	 * 
	 * Name under which  is registered
	 * in . Default is cylinder.
	 */
	SHAPE_CYLINDER: 'cylinder',
	
	/**
	 * Variable: SHAPE_SWIMLANE
	 * 
	 * Name under which  is registered
	 * in . Default is swimlane.
	 */
	SHAPE_SWIMLANE: 'swimlane',
		
	/**
	 * Variable: SHAPE_CONNECTOR
	 * 
	 * Name under which  is registered
	 * in . Default is connector.
	 */
	SHAPE_CONNECTOR: 'connector',
		
	/**
	 * Variable: SHAPE_ACTOR
	 * 
	 * Name under which  is registered
	 * in . Default is actor.
	 */
	SHAPE_ACTOR: 'actor',
		
	/**
	 * Variable: SHAPE_CLOUD
	 * 
	 * Name under which  is registered
	 * in . Default is cloud.
	 */
	SHAPE_CLOUD: 'cloud',
		
	/**
	 * Variable: SHAPE_TRIANGLE
	 * 
	 * Name under which  is registered
	 * in . Default is triangle.
	 */
	SHAPE_TRIANGLE: 'triangle',
		
	/**
	 * Variable: SHAPE_HEXAGON
	 * 
	 * Name under which  is registered
	 * in . Default is hexagon.
	 */
	SHAPE_HEXAGON: 'hexagon',

	/**
	 * Variable: ARROW_CLASSIC
	 * 
	 * Constant for classic arrow markers.
	 */
	ARROW_CLASSIC: 'classic',

	/**
	 * Variable: ARROW_BLOCK
	 * 
	 * Constant for block arrow markers.
	 */
	ARROW_BLOCK: 'block',

	/**
	 * Variable: ARROW_OPEN
	 * 
	 * Constant for open arrow markers.
	 */
	ARROW_OPEN: 'open',

	/**
	 * Variable: ARROW_OVAL
	 * 
	 * Constant for oval arrow markers.
	 */
	ARROW_OVAL: 'oval',

	/**
	 * Variable: ARROW_DIAMOND
	 * 
	 * Constant for diamond arrow markers.
	 */
	ARROW_DIAMOND: 'diamond',

	/**
	 * Variable: ARROW_DIAMOND
	 * 
	 * Constant for diamond arrow markers.
	 */
	ARROW_DIAMOND_THIN: 'diamondThin',

	/**
	 * Variable: ALIGN_LEFT
	 * 
	 * Constant for left horizontal alignment. Default is left.
	 */
	ALIGN_LEFT: 'left',

	/**
	 * Variable: ALIGN_CENTER
	 * 
	 * Constant for center horizontal alignment. Default is center.
	 */
	ALIGN_CENTER: 'center',

	/**
	 * Variable: ALIGN_RIGHT
	 * 
	 * Constant for right horizontal alignment. Default is right.
	 */
	ALIGN_RIGHT: 'right',

	/**
	 * Variable: ALIGN_TOP
	 * 
	 * Constant for top vertical alignment. Default is top.
	 */
	ALIGN_TOP: 'top',

	/**
	 * Variable: ALIGN_MIDDLE
	 * 
	 * Constant for middle vertical alignment. Default is middle.
	 */
	ALIGN_MIDDLE: 'middle',

	/**
	 * Variable: ALIGN_BOTTOM
	 * 
	 * Constant for bottom vertical alignment. Default is bottom.
	 */
	ALIGN_BOTTOM: 'bottom',

	/**
	 * Variable: DIRECTION_NORTH
	 * 
	 * Constant for direction north. Default is north.
	 */
	DIRECTION_NORTH: 'north',

	/**
	 * Variable: DIRECTION_SOUTH
	 * 
	 * Constant for direction south. Default is south.
	 */
	DIRECTION_SOUTH: 'south',

	/**
	 * Variable: DIRECTION_EAST
	 * 
	 * Constant for direction east. Default is east.
	 */
	DIRECTION_EAST: 'east',

	/**
	 * Variable: DIRECTION_WEST
	 * 
	 * Constant for direction west. Default is west.
	 */
	DIRECTION_WEST: 'west',

	/**
	 * Variable: DIRECTION_MASK_NONE
	 * 
	 * Constant for no direction.
	 */
	DIRECTION_MASK_NONE: 0,

	/**
	 * Variable: DIRECTION_MASK_WEST
	 * 
	 * Bitwise mask for west direction.
	 */
	DIRECTION_MASK_WEST: 1,
	
	/**
	 * Variable: DIRECTION_MASK_NORTH
	 * 
	 * Bitwise mask for north direction.
	 */
	DIRECTION_MASK_NORTH: 2,

	/**
	 * Variable: DIRECTION_MASK_SOUTH
	 * 
	 * Bitwise mask for south direction.
	 */
	DIRECTION_MASK_SOUTH: 4,

	/**
	 * Variable: DIRECTION_MASK_EAST
	 * 
	 * Bitwise mask for east direction.
	 */
	DIRECTION_MASK_EAST: 8,
	
	/**
	 * Variable: DIRECTION_MASK_ALL
	 * 
	 * Bitwise mask for all directions.
	 */
	DIRECTION_MASK_ALL: 15,

	/**
	 * Variable: ELBOW_VERTICAL
	 * 
	 * Constant for elbow vertical. Default is horizontal.
	 */
	ELBOW_VERTICAL: 'vertical',

	/**
	 * Variable: ELBOW_HORIZONTAL
	 * 
	 * Constant for elbow horizontal. Default is horizontal.
	 */
	ELBOW_HORIZONTAL: 'horizontal',

	/**
	 * Variable: EDGESTYLE_ELBOW
	 * 
	 * Name of the elbow edge style. Can be used as a string value
	 * for the STYLE_EDGE style.
	 */
	EDGESTYLE_ELBOW: 'elbowEdgeStyle',

	/**
	 * Variable: EDGESTYLE_ENTITY_RELATION
	 * 
	 * Name of the entity relation edge style. Can be used as a string value
	 * for the STYLE_EDGE style.
	 */
	EDGESTYLE_ENTITY_RELATION: 'entityRelationEdgeStyle',

	/**
	 * Variable: EDGESTYLE_LOOP
	 * 
	 * Name of the loop edge style. Can be used as a string value
	 * for the STYLE_EDGE style.
	 */
	EDGESTYLE_LOOP: 'loopEdgeStyle',

	/**
	 * Variable: EDGESTYLE_SIDETOSIDE
	 * 
	 * Name of the side to side edge style. Can be used as a string value
	 * for the STYLE_EDGE style.
	 */
	EDGESTYLE_SIDETOSIDE: 'sideToSideEdgeStyle',

	/**
	 * Variable: EDGESTYLE_TOPTOBOTTOM
	 * 
	 * Name of the top to bottom edge style. Can be used as a string value
	 * for the STYLE_EDGE style.
	 */
	EDGESTYLE_TOPTOBOTTOM: 'topToBottomEdgeStyle',

	/**
	 * Variable: EDGESTYLE_ORTHOGONAL
	 * 
	 * Name of the generic orthogonal edge style. Can be used as a string value
	 * for the STYLE_EDGE style.
	 */
	EDGESTYLE_ORTHOGONAL: 'orthogonalEdgeStyle',

	/**
	 * Variable: EDGESTYLE_SEGMENT
	 * 
	 * Name of the generic segment edge style. Can be used as a string value
	 * for the STYLE_EDGE style.
	 */
	EDGESTYLE_SEGMENT: 'segmentEdgeStyle',
 
	/**
	 * Variable: PERIMETER_ELLIPSE
	 * 
	 * Name of the ellipse perimeter. Can be used as a string value
	 * for the STYLE_PERIMETER style.
	 */
	PERIMETER_ELLIPSE: 'ellipsePerimeter',

	/**
	 * Variable: PERIMETER_RECTANGLE
	 *
	 * Name of the rectangle perimeter. Can be used as a string value
	 * for the STYLE_PERIMETER style.
	 */
	PERIMETER_RECTANGLE: 'rectanglePerimeter',

	/**
	 * Variable: PERIMETER_RHOMBUS
	 * 
	 * Name of the rhombus perimeter. Can be used as a string value
	 * for the STYLE_PERIMETER style.
	 */
	PERIMETER_RHOMBUS: 'rhombusPerimeter',

	/**
	 * Variable: PERIMETER_TRIANGLE
	 * 
	 * Name of the triangle perimeter. Can be used as a string value
	 * for the STYLE_PERIMETER style.
	 */
	PERIMETER_TRIANGLE: 'trianglePerimeter'

};
/**
 * $Id: mxEventObject.js,v 1.11 2011-09-09 10:29:05 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 * Class: mxEventObject
 * 
 * The mxEventObject is a wrapper for all properties of a single event.
 * Additionally, it also offers functions to consume the event and check if it
 * was consumed as follows:
 * 
 * (code)
 * evt.consume();
 * INV: evt.isConsumed() == true
 * (end)
 * 
 * Constructor: mxEventObject
 *
 * Constructs a new event object with the specified name. An optional
 * sequence of key, value pairs can be appended to define properties.
 * 
 * Example:
 *
 * (code)
 * new mxEventObject("eventName", key1, val1, .., keyN, valN)
 * (end)
 */
function mxEventObject(name)
{
	this.name = name;
	this.properties = [];
	
	for (var i = 1; i < arguments.length; i += 2)
	{
		if (arguments[i + 1] != null)
		{
			this.properties[arguments[i]] = arguments[i + 1];
		}
	}
};

/**
 * Variable: name
 *
 * Holds the name.
 */
mxEventObject.prototype.name = null;

/**
 * Variable: properties
 *
 * Holds the properties as an associative array.
 */
mxEventObject.prototype.properties = null;

/**
 * Variable: consumed
 *
 * Holds the consumed state. Default is false.
 */
mxEventObject.prototype.consumed = false;

/**
 * Function: getName
 * 
 * Returns .
 */
mxEventObject.prototype.getName = function()
{
	return this.name;
};

/**
 * Function: getProperties
 * 
 * Returns .
 */
mxEventObject.prototype.getProperties = function()
{
	return this.properties;
};

/**
 * Function: getProperty
 * 
 * Returns the property for the given key.
 */
mxEventObject.prototype.getProperty = function(key)
{
	return this.properties[key];
};

/**
 * Function: isConsumed
 *
 * Returns true if the event has been consumed.
 */
mxEventObject.prototype.isConsumed = function()
{
	return this.consumed;
};

/**
 * Function: consume
 *
 * Consumes the event.
 */
mxEventObject.prototype.consume = function()
{
	this.consumed = true;
};
/**
 * $Id: mxMouseEvent.js,v 1.20 2011-03-02 17:24:39 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 * Class: mxMouseEvent
 * 
 * Base class for all mouse events in mxGraph. A listener for this event should
 * implement the following methods:
 * 
 * (code)
 * graph.addMouseListener(
 * {
 *   mouseDown: function(sender, evt)
 *   {
 *     mxLog.debug('mouseDown');
 *   },
 *   mouseMove: function(sender, evt)
 *   {
 *     mxLog.debug('mouseMove');
 *   },
 *   mouseUp: function(sender, evt)
 *   {
 *     mxLog.debug('mouseUp');
 *   }
 * });
 * (end)
 * 
 * Constructor: mxMouseEvent
 *
 * Constructs a new event object for the given arguments.
 * 
 * Parameters:
 * 
 * evt - Native mouse event.
 * state - Optional  under the mouse.
 * 
 */
function mxMouseEvent(evt, state)
{
	this.evt = evt;
	this.state = state;
};

/**
 * Variable: consumed
 *
 * Holds the consumed state of this event.
 */
mxMouseEvent.prototype.consumed = false;

/**
 * Variable: evt
 *
 * Holds the inner event object.
 */
mxMouseEvent.prototype.evt = null;

/**
 * Variable: graphX
 *
 * Holds the x-coordinate of the event in the graph. This value is set in
 * .
 */
mxMouseEvent.prototype.graphX = null;

/**
 * Variable: graphY
 *
 * Holds the y-coordinate of the event in the graph. This value is set in
 * .
 */
mxMouseEvent.prototype.graphY = null;

/**
 * Variable: state
 *
 * Holds the optional  associated with this event.
 */
mxMouseEvent.prototype.state = null;

/**
 * Function: getEvent
 * 
 * Returns .
 */
mxMouseEvent.prototype.getEvent = function()
{
	return this.evt;
};

/**
 * Function: getSource
 * 
 * Returns the target DOM element using  for .
 */
mxMouseEvent.prototype.getSource = function()
{
	return mxEvent.getSource(this.evt);
};

/**
 * Function: isSource
 * 
 * Returns true if the given  is the source of .
 */
mxMouseEvent.prototype.isSource = function(shape)
{
	if (shape != null)
	{
		var source = this.getSource();
		
		while (source != null)
		{
			if (source == shape.node)
			{
				return true;
			}
	
			source = source.parentNode;
		}
	}
	
	return false;
};

/**
 * Function: getX
 * 
 * Returns .
 */
mxMouseEvent.prototype.getX = function()
{
	return mxEvent.getClientX(this.getEvent());
};

/**
 * Function: getY
 * 
 * Returns .
 */
mxMouseEvent.prototype.getY = function()
{
	return mxEvent.getClientY(this.getEvent());
};

/**
 * Function: getGraphX
 * 
 * Returns .
 */
mxMouseEvent.prototype.getGraphX = function()
{
	return this.graphX;
};

/**
 * Function: getGraphY
 * 
 * Returns .
 */
mxMouseEvent.prototype.getGraphY = function()
{
	return this.graphY;
};

/**
 * Function: getState
 * 
 * Returns .
 */
mxMouseEvent.prototype.getState = function()
{
	return this.state;
};

/**
 * Function: getCell
 * 
 * Returns the  in  is not null.
 */
mxMouseEvent.prototype.getCell = function()
{
	var state = this.getState();
	
	if (state != null)
	{
		return state.cell;
	}
	
	return null;
};

/**
 * Function: isPopupTrigger
 *
 * Returns true if the event is a popup trigger.
 */
mxMouseEvent.prototype.isPopupTrigger = function()
{
	return mxEvent.isPopupTrigger(this.getEvent());
};

/**
 * Function: isConsumed
 *
 * Returns .
 */
mxMouseEvent.prototype.isConsumed = function()
{
	return this.consumed;
};

/**
 * Function: consume
 *
 * Sets  to true and invokes preventDefault on the native event
 * if such a method is defined. This is used mainly to avoid the cursor from
 * being changed to a text cursor in Webkit. You can use the preventDefault
 * flag to disable this functionality.
 * 
 * Parameters:
 * 
 * preventDefault - Specifies if the native event should be canceled. Default
 * is true.
 */
mxMouseEvent.prototype.consume = function(preventDefault)
{
	preventDefault = (preventDefault != null) ? preventDefault : true;
	
	if (preventDefault && this.evt.preventDefault)
	{
		this.evt.preventDefault();
	}

	// Workaround for images being dragged in IE
	this.evt.returnValue = false;
	
	// Sets local consumed state
	this.consumed = true;
};
/**
 * $Id: mxEventSource.js,v 1.25 2012-04-16 10:54:20 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 * Class: mxEventSource
 *
 * Base class for objects that dispatch named events. To create a subclass that
 * inherits from mxEventSource, the following code is used.
 *
 * (code)
 * function MyClass() { };
 *
 * MyClass.prototype = new mxEventSource();
 * MyClass.prototype.constructor = MyClass;
 * (end)
 *
 * Known Subclasses:
 *
 * , , , , ,
 * , 
 * 
 * Constructor: mxEventSource
 *
 * Constructs a new event source.
 */
function mxEventSource(eventSource)
{
	this.setEventSource(eventSource);
};

/**
 * Variable: eventListeners
 *
 * Holds the event names and associated listeners in an array. The array
 * contains the event name followed by the respective listener for each
 * registered listener.
 */
mxEventSource.prototype.eventListeners = null;

/**
 * Variable: eventsEnabled
 *
 * Specifies if events can be fired. Default is true.
 */
mxEventSource.prototype.eventsEnabled = true;

/**
 * Variable: eventSource
 *
 * Optional source for events. Default is null.
 */
mxEventSource.prototype.eventSource = null;

/**
 * Function: isEventsEnabled
 * 
 * Returns .
 */
mxEventSource.prototype.isEventsEnabled = function()
{
	return this.eventsEnabled;
};

/**
 * Function: setEventsEnabled
 * 
 * Sets .
 */
mxEventSource.prototype.setEventsEnabled = function(value)
{
	this.eventsEnabled = value;
};

/**
 * Function: getEventSource
 * 
 * Returns .
 */
mxEventSource.prototype.getEventSource = function()
{
	return this.eventSource;
};

/**
 * Function: setEventSource
 * 
 * Sets .
 */
mxEventSource.prototype.setEventSource = function(value)
{
	this.eventSource = value;
};

/**
 * Function: addListener
 *
 * Binds the specified function to the given event name. If no event name
 * is given, then the listener is registered for all events.
 * 
 * The parameters of the listener are the sender and an .
 */
mxEventSource.prototype.addListener = function(name, funct)
{
	if (this.eventListeners == null)
	{
		this.eventListeners = [];
	}
	
	this.eventListeners.push(name);
	this.eventListeners.push(funct);
};

/**
 * Function: removeListener
 *
 * Removes all occurrences of the given listener from .
 */
mxEventSource.prototype.removeListener = function(funct)
{
	if (this.eventListeners != null)
	{
		var i = 0;
		
		while (i < this.eventListeners.length)
		{
			if (this.eventListeners[i+1] == funct)
			{
				this.eventListeners.splice(i, 2);
			}
			else
			{
				i += 2;
			}
		}
	}
};

/**
 * Function: fireEvent
 *
 * Dispatches the given event to the listeners which are registered for
 * the event. The sender argument is optional. The current execution scope
 * ("this") is used for the listener invocation (see ).
 *
 * Example:
 *
 * (code)
 * fireEvent(new mxEventObject("eventName", key1, val1, .., keyN, valN))
 * (end)
 * 
 * Parameters:
 *
 * evt -  that represents the event.
 * sender - Optional sender to be passed to the listener. Default value is
 * the return value of .
 */
mxEventSource.prototype.fireEvent = function(evt, sender)
{
	if (this.eventListeners != null &&
		this.isEventsEnabled())
	{
		if (evt == null)
		{
			evt = new mxEventObject();
		}
		
		if (sender == null)
		{
			sender = this.getEventSource();
		}

		if (sender == null)
		{
			sender = this;
		}

		var args = [sender, evt];
		
		for (var i = 0; i < this.eventListeners.length; i += 2)
		{
			var listen = this.eventListeners[i];
			
			if (listen == null ||
				listen == evt.getName())
			{
				this.eventListeners[i+1].apply(this, args);
			}
		}
	}
};
/**
 * $Id: mxEvent.js,v 1.76 2012-12-07 07:39:03 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
var mxEvent =
{

	/**
	 * Class: mxEvent
	 * 
	 * Cross-browser DOM event support. For internal event handling,
	 *  and the graph event dispatch loop in  are used.
	 * 
	 * Memory Leaks:
	 * 
	 * Use this class for adding and removing listeners to/from DOM nodes. The
	 *  function is provided to remove all listeners that
	 * have been added using . The function should be invoked when
	 * the last reference is removed in the JavaScript code, typically when the
	 * referenced DOM node is removed from the DOM, and helps to reduce memory
	 * leaks in IE6.
	 * 
	 * Variable: objects
	 * 
	 * Contains all objects where any listener was added using .
	 * This is used to reduce memory leaks in IE, see .
	 */
	objects: [],

	 /**
	  * Function: addListener
	  * 
	  * Binds the function to the specified event on the given element. Use
	  *  in order to bind the "this" keyword inside the function
	  * to a given execution scope.
	  */
	addListener: function()
	{
		var updateListenerList = function(element, eventName, funct)
		{
			if (element.mxListenerList == null)
			{
				element.mxListenerList = [];
				mxEvent.objects.push(element);
			}
			
			var entry = {name: eventName, f: funct};
			element.mxListenerList.push(entry);
		};
		
		if (window.addEventListener)
		{
			return function(element, eventName, funct)
			{
				element.addEventListener(eventName, funct, false);
				updateListenerList(element, eventName, funct);
			};
		}
		else
		{
			return function(element, eventName, funct)
			{
				element.attachEvent('on' + eventName, funct);
				updateListenerList(element, eventName, funct);				
			};
		}
	}(),

	/**
	 * Function: removeListener
	 *
	 * Removes the specified listener from the given element.
	 */
	removeListener: function()
	{
		var updateListener = function(element, eventName, funct)
		{
			if (element.mxListenerList != null)
			{
				var listenerCount = element.mxListenerList.length;
				
				for (var i=0; i 0)
			{
				var entry = list[0];
				mxEvent.removeListener(element, entry.name, entry.f);
			}
		}
	},
	
	/**
	 * Function: redirectMouseEvents
	 *
	 * Redirects the mouse events from the given DOM node to the graph dispatch
	 * loop using the event and given state as event arguments. State can
	 * either be an instance of  or a function that returns an
	 * . The down, move, up and dblClick arguments are optional
	 * functions that take the trigger event as arguments and replace the
	 * default behaviour.
	 */
	redirectMouseEvents: function(node, graph, state, down, move, up, dblClick)
	{
		var getState = function(evt)
		{
			return (typeof(state) == 'function') ? state(evt) : state;
		};
		
		var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
		var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
		var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
		
		mxEvent.addListener(node, md, function (evt)
		{
			if (down != null)
			{
				down(evt);
			}
			else if (!mxEvent.isConsumed(evt))
			{
				graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
					new mxMouseEvent(evt, getState(evt)));
			}
		});
		
		mxEvent.addListener(node, mm, function (evt)
		{
			if (move != null)
			{
				move(evt);
			}
			else if (!mxEvent.isConsumed(evt))
			{
				graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
						new mxMouseEvent(evt, getState(evt)));
			}
		});
		
		mxEvent.addListener(node, mu, function (evt)
		{
			if (up != null)
			{
				up(evt);
			}
			else if (!mxEvent.isConsumed(evt))
			{
				graph.fireMouseEvent(mxEvent.MOUSE_UP,
						new mxMouseEvent(evt, getState(evt)));
			}
		});

		mxEvent.addListener(node, 'dblclick', function (evt)
		{
			if (dblClick != null)
			{
				dblClick(evt);
			}
			else if (!mxEvent.isConsumed(evt))
			{
				var tmp = getState(evt);
				graph.dblClick(evt, (tmp != null) ? tmp.cell : null);
			}
		});
	},

	/**
	 * Function: release
	 * 
	 * Removes the known listeners from the given DOM node and its descendants.
	 * 
	 * Parameters:
	 * 
	 * element - DOM node to remove the listeners from.
	 */
	release: function(element)
	{
		if (element != null)
		{
			mxEvent.removeAllListeners(element);
			
			var children = element.childNodes;
			
			if (children != null)
			{
		        var childCount = children.length;
		        
		        for (var i = 0; i < childCount; i += 1)
		        {
		        	mxEvent.release(children[i]);
		        }
		    }
		}
	},

	/**
	 * Function: addMouseWheelListener
	 * 
	 * Installs the given function as a handler for mouse wheel events. The
	 * function has two arguments: the mouse event and a boolean that specifies
	 * if the wheel was moved up or down.
	 * 
	 * This has been tested with IE 6 and 7, Firefox (all versions), Opera and
	 * Safari. It does currently not work on Safari for Mac.
	 * 
	 * Example:
	 * 
	 * (code)
	 * mxEvent.addMouseWheelListener(function (evt, up)
	 * {
	 *   mxLog.show();
	 *   mxLog.debug('mouseWheel: up='+up);
	 * });
	 *(end)
	 * 
	 * Parameters:
	 * 
	 * funct - Handler function that takes the event argument and a boolean up
	 * argument for the mousewheel direction.
	 */
	addMouseWheelListener: function(funct)
	{
		if (funct != null)
		{
			var wheelHandler = function(evt)
			{
				// IE does not give an event object but the
				// global event object is the mousewheel event
				// at this point in time.
				if (evt == null)
				{
					evt = window.event;
				}
			
				var delta = 0;
				
				if (mxClient.IS_NS && !mxClient.IS_SF && !mxClient.IS_GC)
				{
					delta = -evt.detail/2;
				}
				else
				{
					delta = evt.wheelDelta/120;
				}
				
				// Handles the event using the given function
				if (delta != 0)
				{
					funct(evt, delta > 0);
				}
			};
	
			// Webkit has NS event API, but IE event name and details 
			if (mxClient.IS_NS)
			{
				var eventName = (mxClient.IS_SF || 	mxClient.IS_GC) ?
						'mousewheel' : 'DOMMouseScroll';
				mxEvent.addListener(window, eventName, wheelHandler);
			}
			else
			{
				// TODO: Does not work with Safari and Chrome but it should be
				// working as tested in etc/markup/wheel.html
				mxEvent.addListener(document, 'mousewheel', wheelHandler);
			}
		}
	},
	
	/**
	 * Function: disableContextMenu
	 *
	 * Disables the context menu for the given element.
	 */
	disableContextMenu: function()
	{
		if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9))
		{
			return function(element)
			{
				mxEvent.addListener(element, 'contextmenu', function()
				{
					return false;
				});
			};
		}
		else
		{
			return function(element)
			{
				element.setAttribute('oncontextmenu', 'return false;');
			};		
		}
	}(),
	
	/**
	 * Function: getSource
	 * 
	 * Returns the event's target or srcElement depending on the browser.
	 */
	getSource: function(evt)
	{
		return (evt.srcElement != null) ? evt.srcElement : evt.target;
	},

	/**
	 * Function: isConsumed
	 * 
	 * Returns true if the event has been consumed using .
	 */
	isConsumed: function(evt)
	{
		return evt.isConsumed != null && evt.isConsumed;
	},

	/**
	 * Function: isLeftMouseButton
	 * 
	 * Returns true if the left mouse button is pressed for the given event.
	 * To check if a button is pressed during a mouseMove you should use the
	 *  property.
	 */
	isLeftMouseButton: function(evt)
	{
		return evt.button == ((mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9)) ? 1 : 0);
	},
	
	/**
	 * Function: isRightMouseButton
	 * 
	 * Returns true if the right mouse button was pressed. Note that this
	 * button might not be available on some systems. For handling a popup
	 * trigger  should be used.
	 */
	isRightMouseButton: function(evt)
	{
		return evt.button == 2;
	},

	/**
	 * Function: isPopupTrigger
	 * 
	 * Returns true if the event is a popup trigger. This implementation
	 * returns true if the right mouse button or shift was pressed.
	 */
	isPopupTrigger: function(evt)
	{
		return mxEvent.isRightMouseButton(evt) ||
			(mxEvent.isShiftDown(evt) &&
			!mxEvent.isControlDown(evt));
	},

	/**
	 * Function: isShiftDown
	 * 
	 * Returns true if the shift key is pressed for the given event.
	 */
	isShiftDown: function(evt)
	{
		return (evt != null) ? evt.shiftKey : false;
	},

	/**
	 * Function: isAltDown
	 * 
	 * Returns true if the alt key is pressed for the given event.
	 */
	isAltDown: function(evt)
	{
		return (evt != null) ? evt.altKey : false;
	},

	/**
	 * Function: isControlDown
	 * 
	 * Returns true if the control key is pressed for the given event.
	 */
	isControlDown: function(evt)
	{
		return (evt != null) ? evt.ctrlKey : false;
	},

	/**
	 * Function: isMetaDown
	 * 
	 * Returns true if the meta key is pressed for the given event.
	 */
	isMetaDown: function(evt)
	{
		return (evt != null) ? evt.metaKey : false;
	},

	/**
	 * Function: getMainEvent
	 * 
	 * Returns the touch or mouse event that contains the mouse coordinates.
	 */
	getMainEvent: function(e)
	{
		if ((e.type == 'touchstart' || e.type == 'touchmove') &&
			e.touches != null && e.touches[0] != null)
		{
			e = e.touches[0];
		}
		else if (e.type == 'touchend' && e.changedTouches != null &&
			e.changedTouches[0] != null)
		{
			e = e.changedTouches[0];
		}
		
		return e;
	},
	
	/**
	 * Function: getClientX
	 * 
	 * Returns true if the meta key is pressed for the given event.
	 */
	getClientX: function(e)
	{
		return mxEvent.getMainEvent(e).clientX;
	},

	/**
	 * Function: getClientY
	 * 
	 * Returns true if the meta key is pressed for the given event.
	 */
	getClientY: function(e)
	{
		return mxEvent.getMainEvent(e).clientY;
	},

	/**
	 * Function: consume
	 * 
	 * Consumes the given event.
	 * 
	 * Parameters:
	 * 
	 * evt - Native event to be consumed.
	 * preventDefault - Optional boolean to prevent the default for the event.
	 * Default is true.
	 * stopPropagation - Option boolean to stop event propagation. Default is
	 * true.
	 */
	consume: function(evt, preventDefault, stopPropagation)
	{
		preventDefault = (preventDefault != null) ? preventDefault : true;
		stopPropagation = (stopPropagation != null) ? stopPropagation : true;
		
		if (preventDefault)
		{
			if (evt.preventDefault)
			{
				if (stopPropagation)
				{
					evt.stopPropagation();
				}
				
				evt.preventDefault();
			}
			else if (stopPropagation)
			{
				evt.cancelBubble = true;
			}
		}

		// Opera
		evt.isConsumed = true;

		// Other browsers
		evt.returnValue = false;
	},
	
	//
	// Special handles in mouse events
	//
	
	/**
	 * Variable: LABEL_HANDLE
	 * 
	 * Index for the label handle in an mxMouseEvent. This should be a negative
	 * value that does not interfere with any possible handle indices. Default
	 * is -1.
	 */
	LABEL_HANDLE: -1,
	
	/**
	 * Variable: ROTATION_HANDLE
	 * 
	 * Index for the rotation handle in an mxMouseEvent. This should be a
	 * negative value that does not interfere with any possible handle indices.
	 * Default is -2.
	 */
	ROTATION_HANDLE: -2,
	
	//
	// Event names
	//
	
	/**
	 * Variable: MOUSE_DOWN
	 *
	 * Specifies the event name for mouseDown.
	 */
	MOUSE_DOWN: 'mouseDown',
	
	/**
	 * Variable: MOUSE_MOVE
	 *
	 * Specifies the event name for mouseMove. 
	 */
	MOUSE_MOVE: 'mouseMove',
	
	/**
	 * Variable: MOUSE_UP
	 *
	 * Specifies the event name for mouseUp. 
	 */
	MOUSE_UP: 'mouseUp',

	/**
	 * Variable: ACTIVATE
	 *
	 * Specifies the event name for activate.
	 */
	ACTIVATE: 'activate',

	/**
	 * Variable: RESIZE_START
	 *
	 * Specifies the event name for resizeStart.
	 */
	RESIZE_START: 'resizeStart',

	/**
	 * Variable: RESIZE
	 *
	 * Specifies the event name for resize.
	 */
	RESIZE: 'resize',

	/**
	 * Variable: RESIZE_END
	 *
	 * Specifies the event name for resizeEnd.
	 */
	RESIZE_END: 'resizeEnd',

	/**
	 * Variable: MOVE_START
	 *
	 * Specifies the event name for moveStart.
	 */
	MOVE_START: 'moveStart',

	/**
	 * Variable: MOVE
	 *
	 * Specifies the event name for move.
	 */
	MOVE: 'move',

	/**
	 * Variable: MOVE_END
	 *
	 * Specifies the event name for moveEnd.
	 */
	MOVE_END: 'moveEnd',

	/**
	 * Variable: PAN_START
	 *
	 * Specifies the event name for panStart.
	 */
	PAN_START: 'panStart',

	/**
	 * Variable: PAN
	 *
	 * Specifies the event name for pan.
	 */
	PAN: 'pan',

	/**
	 * Variable: PAN_END
	 *
	 * Specifies the event name for panEnd.
	 */
	PAN_END: 'panEnd',

	/**
	 * Variable: MINIMIZE
	 *
	 * Specifies the event name for minimize.
	 */
	MINIMIZE: 'minimize',

	/**
	 * Variable: NORMALIZE
	 *
	 * Specifies the event name for normalize.
	 */
	NORMALIZE: 'normalize',

	/**
	 * Variable: MAXIMIZE
	 *
	 * Specifies the event name for maximize.
	 */
	MAXIMIZE: 'maximize',

	/**
	 * Variable: HIDE
	 *
	 * Specifies the event name for hide.
	 */
	HIDE: 'hide',

	/**
	 * Variable: SHOW
	 *
	 * Specifies the event name for show.
	 */
	SHOW: 'show',

	/**
	 * Variable: CLOSE
	 *
	 * Specifies the event name for close.
	 */
	CLOSE: 'close',

	/**
	 * Variable: DESTROY
	 *
	 * Specifies the event name for destroy.
	 */
	DESTROY: 'destroy',

	/**
	 * Variable: REFRESH
	 *
	 * Specifies the event name for refresh.
	 */
	REFRESH: 'refresh',

	/**
	 * Variable: SIZE
	 *
	 * Specifies the event name for size.
	 */
	SIZE: 'size',
	
	/**
	 * Variable: SELECT
	 *
	 * Specifies the event name for select.
	 */
	SELECT: 'select',

	/**
	 * Variable: FIRED
	 *
	 * Specifies the event name for fired.
	 */
	FIRED: 'fired',

	/**
	 * Variable: GET
	 *
	 * Specifies the event name for get.
	 */
	GET: 'get',

	/**
	 * Variable: RECEIVE
	 *
	 * Specifies the event name for receive.
	 */
	RECEIVE: 'receive',

	/**
	 * Variable: CONNECT
	 *
	 * Specifies the event name for connect.
	 */
	CONNECT: 'connect',

	/**
	 * Variable: DISCONNECT
	 *
	 * Specifies the event name for disconnect.
	 */
	DISCONNECT: 'disconnect',

	/**
	 * Variable: SUSPEND
	 *
	 * Specifies the event name for suspend.
	 */
	SUSPEND: 'suspend',

	/**
	 * Variable: RESUME
	 *
	 * Specifies the event name for suspend.
	 */
	RESUME: 'resume',

	/**
	 * Variable: MARK
	 *
	 * Specifies the event name for mark.
	 */
	MARK: 'mark',

	/**
	 * Variable: SESSION
	 *
	 * Specifies the event name for session.
	 */
	SESSION: 'session',

	/**
	 * Variable: ROOT
	 *
	 * Specifies the event name for root.
	 */
	ROOT: 'root',

	/**
	 * Variable: POST
	 *
	 * Specifies the event name for post.
	 */
	POST: 'post',

	/**
	 * Variable: OPEN
	 *
	 * Specifies the event name for open.
	 */
	OPEN: 'open',

	/**
	 * Variable: SAVE
	 *
	 * Specifies the event name for open.
	 */
	SAVE: 'save',

	/**
	 * Variable: BEFORE_ADD_VERTEX
	 *
	 * Specifies the event name for beforeAddVertex.
	 */
	BEFORE_ADD_VERTEX: 'beforeAddVertex',

	/**
	 * Variable: ADD_VERTEX
	 *
	 * Specifies the event name for addVertex.
	 */
	ADD_VERTEX: 'addVertex',

	/**
	 * Variable: AFTER_ADD_VERTEX
	 *
	 * Specifies the event name for afterAddVertex.
	 */
	AFTER_ADD_VERTEX: 'afterAddVertex',

	/**
	 * Variable: DONE
	 *
	 * Specifies the event name for done.
	 */
	DONE: 'done',

	/**
	 * Variable: EXECUTE
	 *
	 * Specifies the event name for execute.
	 */
	EXECUTE: 'execute',

	/**
	 * Variable: BEGIN_UPDATE
	 *
	 * Specifies the event name for beginUpdate.
	 */
	BEGIN_UPDATE: 'beginUpdate',

	/**
	 * Variable: END_UPDATE
	 *
	 * Specifies the event name for endUpdate.
	 */
	END_UPDATE: 'endUpdate',

	/**
	 * Variable: BEFORE_UNDO
	 *
	 * Specifies the event name for beforeUndo.
	 */
	BEFORE_UNDO: 'beforeUndo',

	/**
	 * Variable: UNDO
	 *
	 * Specifies the event name for undo.
	 */
	UNDO: 'undo',

	/**
	 * Variable: REDO
	 *
	 * Specifies the event name for redo.
	 */
	REDO: 'redo',

	/**
	 * Variable: CHANGE
	 *
	 * Specifies the event name for change.
	 */
	CHANGE: 'change',

	/**
	 * Variable: NOTIFY
	 *
	 * Specifies the event name for notify.
	 */
	NOTIFY: 'notify',

	/**
	 * Variable: LAYOUT_CELLS
	 *
	 * Specifies the event name for layoutCells.
	 */
	LAYOUT_CELLS: 'layoutCells',

	/**
	 * Variable: CLICK
	 *
	 * Specifies the event name for click.
	 */
	CLICK: 'click',

	/**
	 * Variable: SCALE
	 *
	 * Specifies the event name for scale.
	 */
	SCALE: 'scale',

	/**
	 * Variable: TRANSLATE
	 *
	 * Specifies the event name for translate.
	 */
	TRANSLATE: 'translate',

	/**
	 * Variable: SCALE_AND_TRANSLATE
	 *
	 * Specifies the event name for scaleAndTranslate.
	 */
	SCALE_AND_TRANSLATE: 'scaleAndTranslate',

	/**
	 * Variable: UP
	 *
	 * Specifies the event name for up.
	 */
	UP: 'up',

	/**
	 * Variable: DOWN
	 *
	 * Specifies the event name for down.
	 */
	DOWN: 'down',

	/**
	 * Variable: ADD
	 *
	 * Specifies the event name for add.
	 */
	ADD: 'add',

	/**
	 * Variable: REMOVE
	 *
	 * Specifies the event name for remove.
	 */
	REMOVE: 'remove',
	
	/**
	 * Variable: CLEAR
	 *
	 * Specifies the event name for clear.
	 */
	CLEAR: 'clear',

	/**
	 * Variable: ADD_CELLS
	 *
	 * Specifies the event name for addCells.
	 */
	ADD_CELLS: 'addCells',

	/**
	 * Variable: CELLS_ADDED
	 *
	 * Specifies the event name for cellsAdded.
	 */
	CELLS_ADDED: 'cellsAdded',

	/**
	 * Variable: MOVE_CELLS
	 *
	 * Specifies the event name for moveCells.
	 */
	MOVE_CELLS: 'moveCells',

	/**
	 * Variable: CELLS_MOVED
	 *
	 * Specifies the event name for cellsMoved.
	 */
	CELLS_MOVED: 'cellsMoved',

	/**
	 * Variable: RESIZE_CELLS
	 *
	 * Specifies the event name for resizeCells.
	 */
	RESIZE_CELLS: 'resizeCells',

	/**
	 * Variable: CELLS_RESIZED
	 *
	 * Specifies the event name for cellsResized.
	 */
	CELLS_RESIZED: 'cellsResized',

	/**
	 * Variable: TOGGLE_CELLS
	 *
	 * Specifies the event name for toggleCells.
	 */
	TOGGLE_CELLS: 'toggleCells',

	/**
	 * Variable: CELLS_TOGGLED
	 *
	 * Specifies the event name for cellsToggled.
	 */
	CELLS_TOGGLED: 'cellsToggled',

	/**
	 * Variable: ORDER_CELLS
	 *
	 * Specifies the event name for orderCells.
	 */
	ORDER_CELLS: 'orderCells',

	/**
	 * Variable: CELLS_ORDERED
	 *
	 * Specifies the event name for cellsOrdered.
	 */
	CELLS_ORDERED: 'cellsOrdered',

	/**
	 * Variable: REMOVE_CELLS
	 *
	 * Specifies the event name for removeCells.
	 */
	REMOVE_CELLS: 'removeCells',

	/**
	 * Variable: CELLS_REMOVED
	 *
	 * Specifies the event name for cellsRemoved.
	 */
	CELLS_REMOVED: 'cellsRemoved',

	/**
	 * Variable: GROUP_CELLS
	 *
	 * Specifies the event name for groupCells.
	 */
	GROUP_CELLS: 'groupCells',

	/**
	 * Variable: UNGROUP_CELLS
	 *
	 * Specifies the event name for ungroupCells.
	 */
	UNGROUP_CELLS: 'ungroupCells',

	/**
	 * Variable: REMOVE_CELLS_FROM_PARENT
	 *
	 * Specifies the event name for removeCellsFromParent.
	 */
	REMOVE_CELLS_FROM_PARENT: 'removeCellsFromParent',

	/**
	 * Variable: FOLD_CELLS
	 *
	 * Specifies the event name for foldCells.
	 */
	FOLD_CELLS: 'foldCells',

	/**
	 * Variable: CELLS_FOLDED
	 *
	 * Specifies the event name for cellsFolded.
	 */
	CELLS_FOLDED: 'cellsFolded',

	/**
	 * Variable: ALIGN_CELLS
	 *
	 * Specifies the event name for alignCells.
	 */
	ALIGN_CELLS: 'alignCells',

	/**
	 * Variable: LABEL_CHANGED
	 *
	 * Specifies the event name for labelChanged.
	 */
	LABEL_CHANGED: 'labelChanged',

	/**
	 * Variable: CONNECT_CELL
	 *
	 * Specifies the event name for connectCell.
	 */
	CONNECT_CELL: 'connectCell',

	/**
	 * Variable: CELL_CONNECTED
	 *
	 * Specifies the event name for cellConnected.
	 */
	CELL_CONNECTED: 'cellConnected',

	/**
	 * Variable: SPLIT_EDGE
	 *
	 * Specifies the event name for splitEdge.
	 */
	SPLIT_EDGE: 'splitEdge',

	/**
	 * Variable: FLIP_EDGE
	 *
	 * Specifies the event name for flipEdge.
	 */
	FLIP_EDGE: 'flipEdge',

	/**
	 * Variable: START_EDITING
	 *
	 * Specifies the event name for startEditing.
	 */
	START_EDITING: 'startEditing',

	/**
	 * Variable: ADD_OVERLAY
	 *
	 * Specifies the event name for addOverlay.
	 */
	ADD_OVERLAY: 'addOverlay',

	/**
	 * Variable: REMOVE_OVERLAY
	 *
	 * Specifies the event name for removeOverlay.
	 */
	REMOVE_OVERLAY: 'removeOverlay',

	/**
	 * Variable: UPDATE_CELL_SIZE
	 *
	 * Specifies the event name for updateCellSize.
	 */
	UPDATE_CELL_SIZE: 'updateCellSize',

	/**
	 * Variable: ESCAPE
	 *
	 * Specifies the event name for escape.
	 */
	ESCAPE: 'escape',

	/**
	 * Variable: CLICK
	 *
	 * Specifies the event name for click.
	 */
	CLICK: 'click',

	/**
	 * Variable: DOUBLE_CLICK
	 *
	 * Specifies the event name for doubleClick.
	 */
	DOUBLE_CLICK: 'doubleClick',

	/**
	 * Variable: START
	 *
	 * Specifies the event name for start.
	 */
	START: 'start',

	/**
	 * Variable: RESET
	 *
	 * Specifies the event name for reset.
	 */
	RESET: 'reset'

};
/**
 * $Id: mxXmlRequest.js,v 1.38 2012-04-22 10:16:23 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 * Class: mxXmlRequest
 * 
 * XML HTTP request wrapper. See also: ,  and
 * . This class provides a cross-browser abstraction for Ajax
 * requests.
 * 
 * Encoding:
 * 
 * For encoding parameter values, the built-in encodeURIComponent JavaScript
 * method must be used. For automatic encoding of post data in  the
 *  switch can be set to true (default). The encoding
 * will be carried out using the conte type of the page. That is, the page
 * containting the editor should contain a meta tag in the header, eg.
 * 
 * 
 * Example:
 * 
 * (code)
 * var onload = function(req)
 * {
 *   mxUtils.alert(req.getDocumentElement());
 * }
 * 
 * var onerror = function(req)
 * {
 *   mxUtils.alert(req.getStatus());
 * }
 * new mxXmlRequest(url, 'key=value').send(onload, onerror);
 * (end)
 * 
 * Sends an asynchronous POST request to the specified URL.
 * 
 * Example:
 * 
 * (code)
 * var req = new mxXmlRequest(url, 'key=value', 'POST', false);
 * req.send();
 * mxUtils.alert(req.getDocumentElement());
 * (end)
 * 
 * Sends a synchronous POST request to the specified URL.
 * 
 * Example:
 * 
 * (code)
 * var encoder = new mxCodec();
 * var result = encoder.encode(graph.getModel());
 * var xml = encodeURIComponent(mxUtils.getXml(result));
 * new mxXmlRequest(url, 'xml='+xml).send();
 * (end)
 * 
 * Sends an encoded graph model to the specified URL using xml as the
 * parameter name. The parameter can then be retrieved in C# as follows:
 * 
 * (code)
 * string xml = HttpUtility.UrlDecode(context.Request.Params["xml"]);
 * (end)
 * 
 * Or in Java as follows:
 * 
 * (code)
 * String xml = URLDecoder.decode(request.getParameter("xml"), "UTF-8").replace("\n", "
");
 * (end)
 *
 * Note that the linefeeds should only be replaced if the XML is
 * processed in Java, for example when creating an image.
 * 
 * Constructor: mxXmlRequest
 * 
 * Constructs an XML HTTP request.
 * 
 * Parameters:
 * 
 * url - Target URL of the request.
 * params - Form encoded parameters to send with a POST request.
 * method - String that specifies the request method. Possible values are
 * POST and GET. Default is POST.
 * async - Boolean specifying if an asynchronous request should be used.
 * Default is true.
 * username - String specifying the username to be used for the request.
 * password - String specifying the password to be used for the request.
 */
function mxXmlRequest(url, params, method, async, username, password)
{
	this.url = url;
	this.params = params;
	this.method = method || 'POST';
	this.async = (async != null) ? async : true;
	this.username = username;
	this.password = password;
};

/**
 * Variable: url
 * 
 * Holds the target URL of the request.
 */
mxXmlRequest.prototype.url = null;

/**
 * Variable: params
 * 
 * Holds the form encoded data for the POST request.
 */
mxXmlRequest.prototype.params = null;

/**
 * Variable: method
 * 
 * Specifies the request method. Possible values are POST and GET. Default
 * is POST.
 */
mxXmlRequest.prototype.method = null;

/**
 * Variable: async
 * 
 * Boolean indicating if the request is asynchronous.
 */
mxXmlRequest.prototype.async = null;

/**
 * Variable: binary
 * 
 * Boolean indicating if the request is binary. This option is ignored in IE.
 * In all other browsers the requested mime type is set to
 * text/plain; charset=x-user-defined. Default is false.
 */
mxXmlRequest.prototype.binary = false;

/**
 * Variable: username
 * 
 * Specifies the username to be used for authentication.
 */
mxXmlRequest.prototype.username = null;

/**
 * Variable: password
 * 
 * Specifies the password to be used for authentication.
 */
mxXmlRequest.prototype.password = null;

/**
 * Variable: request
 * 
 * Holds the inner, browser-specific request object.
 */
mxXmlRequest.prototype.request = null;

/**
 * Function: isBinary
 * 
 * Returns .
 */
mxXmlRequest.prototype.isBinary = function()
{
	return this.binary;
};

/**
 * Function: setBinary
 * 
 * Sets .
 */
mxXmlRequest.prototype.setBinary = function(value)
{
	this.binary = value;
};

/**
 * Function: getText
 * 
 * Returns the response as a string.
 */
mxXmlRequest.prototype.getText = function()
{
	return this.request.responseText;
};

/**
 * Function: isReady
 * 
 * Returns true if the response is ready.
 */
mxXmlRequest.prototype.isReady = function()
{
	return this.request.readyState == 4;
};

/**
 * Function: getDocumentElement
 * 
 * Returns the document element of the response XML document.
 */
mxXmlRequest.prototype.getDocumentElement = function()
{
	var doc = this.getXml();
	
	if (doc != null)
	{
		return doc.documentElement;
	}
	
	return null;
};

/**
 * Function: getXml
 * 
 * Returns the response as an XML document. Use  to get
 * the document element of the XML document.
 */
mxXmlRequest.prototype.getXml = function()
{
	var xml = this.request.responseXML;
	
	// Handles missing response headers in IE, the first condition handles
	// the case where responseXML is there, but using its nodes leads to
	// type errors in the mxCellCodec when putting the nodes into a new
	// document. This happens in IE9 standards mode and with XML user
	// objects only, as they are used directly as values in cells.
	if (document.documentMode >= 9 || xml == null || xml.documentElement == null)
	{
		xml = mxUtils.parseXml(this.request.responseText);
	}
	
	return xml;
};

/**
 * Function: getText
 * 
 * Returns the response as a string.
 */
mxXmlRequest.prototype.getText = function()
{
	return this.request.responseText;
};

/**
 * Function: getStatus
 * 
 * Returns the status as a number, eg. 404 for "Not found" or 200 for "OK".
 * Note: The NS_ERROR_NOT_AVAILABLE for invalid responses cannot be cought.
 */
mxXmlRequest.prototype.getStatus = function()
{
	return this.request.status;
};

/**
 * Function: create
 * 
 * Creates and returns the inner  object.
 */
mxXmlRequest.prototype.create = function()
{
	if (window.XMLHttpRequest)
	{
		return function()
		{
			var req = new XMLHttpRequest();
			
			// TODO: Check for overrideMimeType required here?
			if (this.isBinary() && req.overrideMimeType)
			{
				req.overrideMimeType('text/plain; charset=x-user-defined');
			}

			return req;
		};
	}
	else if (typeof(ActiveXObject) != "undefined")
	{
		return function()
		{
			// TODO: Implement binary option
			return new ActiveXObject("Microsoft.XMLHTTP");
		};
	}
}();

/**
 * Function: send
 * 
 * Send the  to the target URL using the specified functions to
 * process the response asychronously.
 * 
 * Parameters:
 * 
 * onload - Function to be invoked if a successful response was received.
 * onerror - Function to be called on any error.
 */
mxXmlRequest.prototype.send = function(onload, onerror)
{
	this.request = this.create();
	
	if (this.request != null)
	{
		if (onload != null)
		{
			this.request.onreadystatechange = mxUtils.bind(this, function()
			{
				if (this.isReady())
				{
					onload(this);
					this.onreadystatechaange = null;
				}
			});
		}

		this.request.open(this.method, this.url, this.async,
			this.username, this.password);
		this.setRequestHeaders(this.request, this.params);
		this.request.send(this.params);
	}
};

/**
 * Function: setRequestHeaders
 * 
 * Sets the headers for the given request and parameters. This sets the
 * content-type to application/x-www-form-urlencoded if any params exist.
 * 
 * Example:
 * 
 * (code)
 * request.setRequestHeaders = function(request, params)
 * {
 *   if (params != null)
 *   {
 *     request.setRequestHeader('Content-Type',
 *             'multipart/form-data');
 *     request.setRequestHeader('Content-Length',
 *             params.length);
 *   }
 * };
 * (end)
 * 
 * Use the code above before calling  if you require a
 * multipart/form-data request.   
 */
mxXmlRequest.prototype.setRequestHeaders = function(request, params)
{
	if (params != null)
	{
		request.setRequestHeader('Content-Type',
			'application/x-www-form-urlencoded');
	}
};

/**
 * Function: simulate
 * 
 * Creates and posts a request to the given target URL using a dynamically
 * created form inside the given document.
 * 
 * Parameters:
 * 
 * docs - Document that contains the form element.
 * target - Target to send the form result to.
 */
mxXmlRequest.prototype.simulate = function(doc, target)
{
	doc = doc || document;
	var old = null;

	if (doc == document)
	{
		old = window.onbeforeunload;		
		window.onbeforeunload = null;
	}
			
	var form = doc.createElement('form');
	form.setAttribute('method', this.method);
	form.setAttribute('action', this.url);

	if (target != null)
	{
		form.setAttribute('target', target);
	}

	form.style.display = 'none';
	form.style.visibility = 'hidden';

	var pars = (this.params.indexOf('&') > 0) ?
		this.params.split('&') :
		this.params.split();

	// Adds the parameters as textareas to the form
	for (var i=0; i 0)
		{
			var name = pars[i].substring(0, pos);
			var value = pars[i].substring(pos+1);
			
			var textarea = doc.createElement('textarea');
			textarea.setAttribute('name', name);
			value = value.replace(/\n/g, '
');
			
			var content = doc.createTextNode(value);
			textarea.appendChild(content);
			form.appendChild(textarea);
		}
	}
	
	doc.body.appendChild(form);
	form.submit();
	doc.body.removeChild(form);

	if (old != null)
	{		
		window.onbeforeunload = old;
	}
};
/**
 * $Id: mxClipboard.js,v 1.29 2010-01-02 09:45:14 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
var mxClipboard =
{
	/**
	 * Class: mxClipboard
	 * 
	 * Singleton that implements a clipboard for graph cells.
	 *
	 * Example:
	 * 
	 * (code)
	 * mxClipboard.copy(graph);
	 * mxClipboard.paste(graph2);
	 * (end)
	 *
	 * This copies the selection cells from the graph to the
	 * clipboard and pastes them into graph2.
	 * 
	 * For fine-grained control of the clipboard data the 
	 * and  functions can be overridden.
	 * 
	 * Variable: STEPSIZE
	 * 
	 * Defines the step size to offset the cells
	 * after each paste operation. Default is 10.
	 */
	STEPSIZE: 10,

	/**
	 * Variable: insertCount
	 * 
	 * Counts the number of times the clipboard data has been inserted.
	 */
	insertCount: 1,

	/**
	 * Variable: cells
	 * 
	 * Holds the array of  currently in the clipboard.
	 */
	cells: null,
	
	/**
	 * Function: isEmpty
	 * 
	 * Returns true if the clipboard currently has not data stored.
	 */
	isEmpty: function()
	{
		return mxClipboard.cells == null;
	},

	/**
	 * Function: cut
	 * 
	 * Cuts the given array of  from the specified graph.
	 * If cells is null then the selection cells of the graph will
	 * be used. Returns the cells that have been cut from the graph.
	 *
	 * Parameters:
	 * 
	 * graph -  that contains the cells to be cut.
	 * cells - Optional array of  to be cut.
	 */
	cut: function(graph, cells)
	{
		cells = mxClipboard.copy(graph, cells);
		mxClipboard.insertCount = 0;
		mxClipboard.removeCells(graph, cells);
		
		return cells;
	},

	/**
	 * Function: removeCells
	 * 
	 * Hook to remove the given cells from the given graph after
	 * a cut operation.
	 *
	 * Parameters:
	 * 
	 * graph -  that contains the cells to be cut.
	 * cells - Array of  to be cut.
	 */
	removeCells: function(graph, cells)
	{
		graph.removeCells(cells);
	},

	/**
	 * Function: copy
	 * 
	 * Copies the given array of  from the specified
	 * graph to .Returns the original array of cells that has
	 * been cloned.
	 * 
	 * Parameters:
	 * 
	 * graph -  that contains the cells to be copied.
	 * cells - Optional array of  to be copied.
	 */
	copy: function(graph, cells)
	{
		cells = cells || graph.getSelectionCells();
		var result = graph.getExportableCells(cells);
		mxClipboard.insertCount = 1;
		mxClipboard.cells = graph.cloneCells(result);

		return result;
	},

	/**
	 * Function: paste
	 * 
	 * Pastes the  into the specified graph restoring
	 * the relation to , if possible. If the parents
	 * are no longer in the graph or invisible then the
	 * cells are added to the graph's default or into the
	 * swimlane under the cell's new location if one exists.
	 * The cells are added to the graph using .
	 * 
	 * Parameters:
	 * 
	 * graph -  to paste the  into.
	 */
	paste: function(graph)
	{
		if (mxClipboard.cells != null)
		{
			var cells = graph.getImportableCells(mxClipboard.cells);
			var delta = mxClipboard.insertCount * mxClipboard.STEPSIZE;
			var parent = graph.getDefaultParent();
			cells = graph.importCells(cells, delta, delta, parent);
			
			// Increments the counter and selects the inserted cells
			mxClipboard.insertCount++;
			graph.setSelectionCells(cells);
		}
	}

};
/**
 * $Id: mxWindow.js,v 1.67 2012-10-11 17:18:51 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 * Class: mxWindow
 * 
 * Basic window inside a document.
 * 
 * Examples:
 * 
 * Creating a simple window.
 *
 * (code)
 * var tb = document.createElement('div');
 * var wnd = new mxWindow('Title', tb, 100, 100, 200, 200, true, true);
 * wnd.setVisible(true); 
 * (end)
 *
 * Creating a window that contains an iframe. 
 * 
 * (code)
 * var frame = document.createElement('iframe');
 * frame.setAttribute('width', '192px');
 * frame.setAttribute('height', '172px');
 * frame.setAttribute('src', 'http://www.example.com/');
 * frame.style.backgroundColor = 'white';
 * 
 * var w = document.body.clientWidth;
 * var h = (document.body.clientHeight || document.documentElement.clientHeight);
 * var wnd = new mxWindow('Title', frame, (w-200)/2, (h-200)/3, 200, 200);
 * wnd.setVisible(true);
 * (end)
 * 
 * To limit the movement of a window, eg. to keep it from being moved beyond
 * the top, left corner the following method can be overridden (recommended):
 * 
 * (code)
 * wnd.setLocation = function(x, y)
 * {
 *   x = Math.max(0, x);
 *   y = Math.max(0, y);
 *   mxWindow.prototype.setLocation.apply(this, arguments);
 * };
 * (end)
 * 
 * Or the following event handler can be used:
 * 
 * (code)
 * wnd.addListener(mxEvent.MOVE, function(e)
 * {
 *   wnd.setLocation(Math.max(0, wnd.getX()), Math.max(0, wnd.getY()));
 * });
 * (end)
 *
 * Event: mxEvent.MOVE_START
 *
 * Fires before the window is moved. The event property contains
 * the corresponding mouse event.
 *
 * Event: mxEvent.MOVE
 *
 * Fires while the window is being moved. The event property
 * contains the corresponding mouse event.
 *
 * Event: mxEvent.MOVE_END
 *
 * Fires after the window is moved. The event property contains
 * the corresponding mouse event.
 *
 * Event: mxEvent.RESIZE_START
 *
 * Fires before the window is resized. The event property contains
 * the corresponding mouse event.
 *
 * Event: mxEvent.RESIZE
 *
 * Fires while the window is being resized. The event property
 * contains the corresponding mouse event.
 *
 * Event: mxEvent.RESIZE_END
 *
 * Fires after the window is resized. The event property contains
 * the corresponding mouse event.
 *
 * Event: mxEvent.MAXIMIZE
 * 
 * Fires after the window is maximized. The event property
 * contains the corresponding mouse event.
 * 
 * Event: mxEvent.MINIMIZE
 * 
 * Fires after the window is minimized. The event property
 * contains the corresponding mouse event.
 * 
 * Event: mxEvent.NORMALIZE
 * 
 * Fires after the window is normalized, that is, it returned from
 * maximized or minimized state. The event property contains the
 * corresponding mouse event.
 *  
 * Event: mxEvent.ACTIVATE
 * 
 * Fires after a window is activated. The previousWindow property
 * contains the previous window. The event sender is the active window.
 * 
 * Event: mxEvent.SHOW
 * 
 * Fires after the window is shown. This event has no properties.
 * 
 * Event: mxEvent.HIDE
 * 
 * Fires after the window is hidden. This event has no properties.
 * 
 * Event: mxEvent.CLOSE
 * 
 * Fires before the window is closed. The event property contains
 * the corresponding mouse event.
 * 
 * Event: mxEvent.DESTROY
 * 
 * Fires before the window is destroyed. This event has no properties.
 * 
 * Constructor: mxWindow
 * 
 * Constructs a new window with the given dimension and title to display
 * the specified content. The window elements use the given style as a
 * prefix for the classnames of the respective window elements, namely,
 * the window title and window pane. The respective postfixes are appended
 * to the given stylename as follows:
 * 
 *   style - Base style for the window.
 *   style+Title - Style for the window title.
 *   style+Pane - Style for the window pane.
 * 
 * The default value for style is mxWindow, resulting in the following
 * classnames for the window elements: mxWindow, mxWindowTitle and
 * mxWindowPane.
 * 
 * If replaceNode is given then the window replaces the given DOM node in
 * the document.
 * 
 * Parameters:
 * 
 * title - String that represents the title of the new window.
 * content - DOM node that is used as the window content.
 * x - X-coordinate of the window location.
 * y - Y-coordinate of the window location.
 * width - Width of the window.
 * height - Optional height of the window. Default is to match the height
 * of the content at the specified width.
 * minimizable - Optional boolean indicating if the window is minimizable.
 * Default is true.
 * movable - Optional boolean indicating if the window is movable. Default
 * is true.
 * replaceNode - Optional DOM node that the window should replace.
 * style - Optional base classname for the window elements. Default is
 * mxWindow.
 */
function mxWindow(title, content, x, y, width, height, minimizable, movable, replaceNode, style)
{
	if (content != null)
	{
		minimizable = (minimizable != null) ? minimizable : true;
		this.content = content;
		this.init(x, y, width, height, style);
		
		this.installMaximizeHandler();
		this.installMinimizeHandler();
		this.installCloseHandler();
		this.setMinimizable(minimizable);
		this.setTitle(title);
		
		if (movable == null || movable)
		{
			this.installMoveHandler();
		}

		if (replaceNode != null && replaceNode.parentNode != null)
		{
			replaceNode.parentNode.replaceChild(this.div, replaceNode);
		}
		else
		{
			document.body.appendChild(this.div);
		}
	}
};

/**
 * Extends mxEventSource.
 */
mxWindow.prototype = new mxEventSource();
mxWindow.prototype.constructor = mxWindow;

/**
 * Variable: closeImage
 * 
 * URL of the image to be used for the close icon in the titlebar.
 */
mxWindow.prototype.closeImage = mxClient.imageBasePath + '/close.gif';

/**
 * Variable: minimizeImage
 * 
 * URL of the image to be used for the minimize icon in the titlebar.
 */
mxWindow.prototype.minimizeImage = mxClient.imageBasePath + '/minimize.gif';
	
/**
 * Variable: normalizeImage
 * 
 * URL of the image to be used for the normalize icon in the titlebar.
 */
mxWindow.prototype.normalizeImage = mxClient.imageBasePath + '/normalize.gif';
	
/**
 * Variable: maximizeImage
 * 
 * URL of the image to be used for the maximize icon in the titlebar.
 */
mxWindow.prototype.maximizeImage = mxClient.imageBasePath + '/maximize.gif';

/**
 * Variable: normalizeImage
 * 
 * URL of the image to be used for the resize icon.
 */
mxWindow.prototype.resizeImage = mxClient.imageBasePath + '/resize.gif';

/**
 * Variable: visible
 * 
 * Boolean flag that represents the visible state of the window.
 */
mxWindow.prototype.visible = false;
	
/**
 * Variable: content
 * 
 * Reference to the DOM node that represents the window content.
 */
mxWindow.prototype.content = false;
	
/**
 * Variable: minimumSize
 * 
 *  that specifies the minimum width and height of the window.
 * Default is (50, 40).
 */
mxWindow.prototype.minimumSize = new mxRectangle(0, 0, 50, 40);

/**
 * Variable: title
 * 
 * Reference to the DOM node (TD) that contains the title.
 */
mxWindow.prototype.title = false;

/**
 * Variable: content
 * 
 * Reference to the DOM node that represents the window content.
 */
mxWindow.prototype.content = false;

/**
 * Variable: destroyOnClose
 * 
 * Specifies if the window should be destroyed when it is closed. If this
 * is false then the window is hidden using . Default is true.
 */
mxWindow.prototype.destroyOnClose = true;

/**
 * Function: init
 * 
 * Initializes the DOM tree that represents the window.
 */
mxWindow.prototype.init = function(x, y, width, height, style)
{
	style = (style != null) ? style : 'mxWindow';
	
	this.div = document.createElement('div');
	this.div.className = style; 
	this.div.style.left = x+'px';
	this.div.style.top = y+'px';
	this.table = document.createElement('table');
	this.table.className = style;
	
	// Workaround for table size problems in FF
	if (width != null)
	{
		if (!mxClient.IS_IE)
		{
			this.div.style.width = width+'px'; 
		}
		
		this.table.style.width = width+'px';
	} 
	
	if (height != null)
	{
		if (!mxClient.IS_IE)
		{
			this.div.style.height = height+'px';
		}
		
		this.table.style.height = height+'px';
	}		
	
	// Creates title row
	var tbody = document.createElement('tbody');
	var tr = document.createElement('tr');
	
	this.title = document.createElement('td');
	this.title.className = style+'Title';
	tr.appendChild(this.title);
	tbody.appendChild(tr);
	
	// Creates content row and table cell
	tr = document.createElement('tr');
	this.td = document.createElement('td');
	this.td.className = style+'Pane';

	this.contentWrapper = document.createElement('div');
	this.contentWrapper.className = style+'Pane';
	this.contentWrapper.style.width = '100%';
	this.contentWrapper.appendChild(this.content);

	// Workaround for div around div restricts height
	// of inner div if outerdiv has hidden overflow
	if (mxClient.IS_IE || this.content.nodeName.toUpperCase() != 'DIV')
	{
		this.contentWrapper.style.height = '100%';
	}

	// Puts all content into the DOM
	this.td.appendChild(this.contentWrapper);
	tr.appendChild(this.td);
	tbody.appendChild(tr);
	this.table.appendChild(tbody);
	this.div.appendChild(this.table);
	
	// Puts the window on top of other windows when clicked
	var activator = mxUtils.bind(this, function(evt)
	{
		this.activate();
	});
	
	var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
	mxEvent.addListener(this.title, md, activator);
	mxEvent.addListener(this.table, md, activator);

	this.hide();
};

/**
 * Function: setTitle
 * 
 * Sets the window title to the given string. HTML markup inside the title
 * will be escaped.
 */
mxWindow.prototype.setTitle = function(title)
{
	// Removes all text content nodes (normally just one)
	var child = this.title.firstChild;
	
	while (child != null)
	{
		var next = child.nextSibling;
		
		if (child.nodeType == mxConstants.NODETYPE_TEXT)
		{
			child.parentNode.removeChild(child);
		}
		
		child = next;
	}
	
	mxUtils.write(this.title, title || '');
};

/**
 * Function: setScrollable
 * 
 * Sets if the window contents should be scrollable.
 */
mxWindow.prototype.setScrollable = function(scrollable)
{
	// Workaround for hang in Presto 2.5.22 (Opera 10.5)
	if (navigator.userAgent.indexOf('Presto/2.5') < 0)
	{
		if (scrollable)
		{
			this.contentWrapper.style.overflow = 'auto';
		}
		else
		{
			this.contentWrapper.style.overflow = 'hidden';
		}
	}
};

/**
 * Function: activate
 * 
 * Puts the window on top of all other windows.
 */
mxWindow.prototype.activate = function()
{
	if (mxWindow.activeWindow != this)
	{
		var style = mxUtils.getCurrentStyle(this.getElement());
		var index = (style != null) ? style.zIndex : 3;

		if (mxWindow.activeWindow)
		{
			var elt = mxWindow.activeWindow.getElement();
			
			if (elt != null && elt.style != null)
			{
				elt.style.zIndex = index;
			}
		}
		
		var previousWindow = mxWindow.activeWindow;
		this.getElement().style.zIndex = parseInt(index) + 1;
		mxWindow.activeWindow = this;
		
		this.fireEvent(new mxEventObject(mxEvent.ACTIVATE, 'previousWindow', previousWindow));
	}
};

/**
 * Function: getElement
 * 
 * Returuns the outermost DOM node that makes up the window.
 */
mxWindow.prototype.getElement = function()
{
	return this.div;
};

/**
 * Function: fit
 * 
 * Makes sure the window is inside the client area of the window.
 */
mxWindow.prototype.fit = function()
{
	mxUtils.fit(this.div);
};

/**
 * Function: isResizable
 * 
 * Returns true if the window is resizable.
 */
mxWindow.prototype.isResizable = function()
{
	if (this.resize != null)
	{
		return this.resize.style.display != 'none';
	}
	
	return false;
};

/**
 * Function: setResizable
 * 
 * Sets if the window should be resizable.
 */
mxWindow.prototype.setResizable = function(resizable)
{
	if (resizable)
	{
		if (this.resize == null)
		{
			this.resize = document.createElement('img');
			this.resize.style.position = 'absolute';
			this.resize.style.bottom = '2px';
			this.resize.style.right = '2px';

			this.resize.setAttribute('src', mxClient.imageBasePath + '/resize.gif');
			this.resize.style.cursor = 'nw-resize';
			
			var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
			var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
			var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';

			mxEvent.addListener(this.resize, md, mxUtils.bind(this, function(evt)
			{
				this.activate();
				var startX = mxEvent.getClientX(evt);
				var startY = mxEvent.getClientY(evt);
				var width = this.div.offsetWidth;
				var height = this.div.offsetHeight;

				// Adds a temporary pair of listeners to intercept
				// the gesture event in the document
				var dragHandler = mxUtils.bind(this, function(evt)
				{
					var dx = mxEvent.getClientX(evt) - startX;
					var dy = mxEvent.getClientY(evt) - startY;

					this.setSize(width + dx, height + dy);
	
					this.fireEvent(new mxEventObject(mxEvent.RESIZE, 'event', evt));
					mxEvent.consume(evt);
				});
				
				var dropHandler = mxUtils.bind(this, function(evt)
				{
					mxEvent.removeListener(document, mm, dragHandler);
					mxEvent.removeListener(document, mu, dropHandler);
	
					this.fireEvent(new mxEventObject(mxEvent.RESIZE_END, 'event', evt));
					mxEvent.consume(evt);
				});
	
				mxEvent.addListener(document, mm, dragHandler);
				mxEvent.addListener(document, mu, dropHandler);

				this.fireEvent(new mxEventObject(mxEvent.RESIZE_START, 'event', evt));
				mxEvent.consume(evt);
			}));

			this.div.appendChild(this.resize);
		}
		else 
		{
			this.resize.style.display = 'inline';
		}
	}
	else if (this.resize != null)
	{
		this.resize.style.display = 'none';
	}
};
	
/**
 * Function: setSize
 * 
 * Sets the size of the window.
 */
mxWindow.prototype.setSize = function(width, height)
{
	width = Math.max(this.minimumSize.width, width);
	height = Math.max(this.minimumSize.height, height);

	// Workaround for table size problems in FF
	if (!mxClient.IS_IE)
	{
		this.div.style.width =  width + 'px';
		this.div.style.height = height + 'px';
	}
	
	this.table.style.width =  width + 'px';
	this.table.style.height = height + 'px';

	if (!mxClient.IS_IE)
	{
		this.contentWrapper.style.height =
			(this.div.offsetHeight - this.title.offsetHeight - 2)+'px';
	}
};
	
/**
 * Function: setMinimizable
 * 
 * Sets if the window is minimizable.
 */
mxWindow.prototype.setMinimizable = function(minimizable)
{
	this.minimize.style.display = (minimizable) ? '' : 'none';
};

/**
 * Function: getMinimumSize
 * 
 * Returns an  that specifies the size for the minimized window.
 * A width or height of 0 means keep the existing width or height. This
 * implementation returns the height of the window title and keeps the width.
 */
mxWindow.prototype.getMinimumSize = function()
{
	return new mxRectangle(0, 0, 0, this.title.offsetHeight);
};

/**
 * Function: installMinimizeHandler
 * 
 * Installs the event listeners required for minimizing the window.
 */
mxWindow.prototype.installMinimizeHandler = function()
{
	this.minimize = document.createElement('img');
	
	this.minimize.setAttribute('src', this.minimizeImage);
	this.minimize.setAttribute('align', 'right');
	this.minimize.setAttribute('title', 'Minimize');
	this.minimize.style.cursor = 'pointer';
	this.minimize.style.marginRight = '1px';
	this.minimize.style.display = 'none';
	
	this.title.appendChild(this.minimize);
	
	var minimized = false;
	var maxDisplay = null;
	var height = null;

	var funct = mxUtils.bind(this, function(evt)
	{
		this.activate();
		
		if (!minimized)
		{
			minimized = true;
			
			this.minimize.setAttribute('src', this.normalizeImage);
			this.minimize.setAttribute('title', 'Normalize');
			this.contentWrapper.style.display = 'none';
			maxDisplay = this.maximize.style.display;
			
			this.maximize.style.display = 'none';
			height = this.table.style.height;
			
			var minSize = this.getMinimumSize();
			
			if (minSize.height > 0)
			{
				if (!mxClient.IS_IE)
				{
					this.div.style.height = minSize.height + 'px';
				}
				
				this.table.style.height = minSize.height + 'px';
			}
			
			if (minSize.width > 0)
			{
				if (!mxClient.IS_IE)
				{
					this.div.style.width = minSize.width + 'px';
				}
				
				this.table.style.width = minSize.width + 'px';
			}
			
			if (this.resize != null)
			{
				this.resize.style.visibility = 'hidden';
			}
			
			this.fireEvent(new mxEventObject(mxEvent.MINIMIZE, 'event', evt));
		}
		else
		{
			minimized = false;
			
			this.minimize.setAttribute('src', this.minimizeImage);
			this.minimize.setAttribute('title', 'Minimize');
			this.contentWrapper.style.display = ''; // default
			this.maximize.style.display = maxDisplay;
			
			if (!mxClient.IS_IE)
			{
				this.div.style.height = height;
			}
			
			this.table.style.height = height;

			if (this.resize != null)
			{
				this.resize.style.visibility = '';
			}
			
			this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt));
		}
		
		mxEvent.consume(evt);
	});
	
	var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
	mxEvent.addListener(this.minimize, md, funct);
};
	
/**
 * Function: setMaximizable
 * 
 * Sets if the window is maximizable.
 */
mxWindow.prototype.setMaximizable = function(maximizable)
{
	this.maximize.style.display = (maximizable) ? '' : 'none';
};

/**
 * Function: installMaximizeHandler
 * 
 * Installs the event listeners required for maximizing the window.
 */
mxWindow.prototype.installMaximizeHandler = function()
{
	this.maximize = document.createElement('img');
	
	this.maximize.setAttribute('src', this.maximizeImage);
	this.maximize.setAttribute('align', 'right');
	this.maximize.setAttribute('title', 'Maximize');
	this.maximize.style.cursor = 'default';
	this.maximize.style.marginLeft = '1px';
	this.maximize.style.cursor = 'pointer';
	this.maximize.style.display = 'none';
	
	this.title.appendChild(this.maximize);
	
	var maximized = false;
	var x = null;
	var y = null;
	var height = null;
	var width = null;

	var funct = mxUtils.bind(this, function(evt)
	{
		this.activate();
		
		if (this.maximize.style.display != 'none')
		{
			if (!maximized)
			{
				maximized = true;
				
				this.maximize.setAttribute('src', this.normalizeImage);
				this.maximize.setAttribute('title', 'Normalize');
				this.contentWrapper.style.display = '';
				this.minimize.style.visibility = 'hidden';
				
				// Saves window state
				x = parseInt(this.div.style.left);
				y = parseInt(this.div.style.top);
				height = this.table.style.height;
				width = this.table.style.width;

				this.div.style.left = '0px';
				this.div.style.top = '0px';

				if (!mxClient.IS_IE)
				{
					this.div.style.height = (document.body.clientHeight-2)+'px';
					this.div.style.width = (document.body.clientWidth-2)+'px';
				}

				this.table.style.width = (document.body.clientWidth-2)+'px';
				this.table.style.height = (document.body.clientHeight-2)+'px';
				
				if (this.resize != null)
				{
					this.resize.style.visibility = 'hidden';
				}

				if (!mxClient.IS_IE)
				{
					var style = mxUtils.getCurrentStyle(this.contentWrapper);
		
					if (style.overflow == 'auto' || this.resize != null)
					{
						this.contentWrapper.style.height =
							(this.div.offsetHeight - this.title.offsetHeight - 2)+'px';
					}
				}

				this.fireEvent(new mxEventObject(mxEvent.MAXIMIZE, 'event', evt));
			}
			else
			{
				maximized = false;
				
				this.maximize.setAttribute('src', this.maximizeImage);
				this.maximize.setAttribute('title', 'Maximize');
				this.contentWrapper.style.display = '';
				this.minimize.style.visibility = '';

				// Restores window state
				this.div.style.left = x+'px';
				this.div.style.top = y+'px';
				
				if (!mxClient.IS_IE)
				{
					this.div.style.height = height;
					this.div.style.width = width;

					var style = mxUtils.getCurrentStyle(this.contentWrapper);
		
					if (style.overflow == 'auto' || this.resize != null)
					{
						this.contentWrapper.style.height =
							(this.div.offsetHeight - this.title.offsetHeight - 2)+'px';
					}
				}
				
				this.table.style.height = height;
				this.table.style.width = width;

				if (this.resize != null)
				{
					this.resize.style.visibility = '';
				}
				
				this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt));
			}
			
			mxEvent.consume(evt);
		}
	});
	
	var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
	mxEvent.addListener(this.maximize, md, funct);
	mxEvent.addListener(this.title, 'dblclick', funct);
};
	
/**
 * Function: installMoveHandler
 * 
 * Installs the event listeners required for moving the window.
 */
mxWindow.prototype.installMoveHandler = function()
{
	this.title.style.cursor = 'move';
	
	var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
	var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
	var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
	
	mxEvent.addListener(this.title, md, mxUtils.bind(this, function(evt)
	{
		var startX = mxEvent.getClientX(evt);
		var startY = mxEvent.getClientY(evt);
		var x = this.getX();
		var y = this.getY();
					
		// Adds a temporary pair of listeners to intercept
		// the gesture event in the document
		var dragHandler = mxUtils.bind(this, function(evt)
		{
			var dx = mxEvent.getClientX(evt) - startX;
			var dy = mxEvent.getClientY(evt) - startY;
			this.setLocation(x + dx, y + dy);
			this.fireEvent(new mxEventObject(mxEvent.MOVE, 'event', evt));
			mxEvent.consume(evt);
		});
		
		var dropHandler = mxUtils.bind(this, function(evt)
		{
			mxEvent.removeListener(document, mm, dragHandler);
			mxEvent.removeListener(document, mu, dropHandler);

			this.fireEvent(new mxEventObject(mxEvent.MOVE_END, 'event', evt));
			mxEvent.consume(evt);
		});

		mxEvent.addListener(document, mm, dragHandler);
		mxEvent.addListener(document, mu, dropHandler);
		
		this.fireEvent(new mxEventObject(mxEvent.MOVE_START, 'event', evt));
		mxEvent.consume(evt);
	}));
};

/**
 * Function: setLocation
 * 
 * Sets the upper, left corner of the window.
 */
 mxWindow.prototype.setLocation = function(x, y)
 {
	this.div.style.left = x + 'px';
	this.div.style.top = y + 'px';
 };

/**
 * Function: getX
 *
 * Returns the current position on the x-axis.
 */
mxWindow.prototype.getX = function()
{
	return parseInt(this.div.style.left);
};

/**
 * Function: getY
 *
 * Returns the current position on the y-axis.
 */
mxWindow.prototype.getY = function()
{
	return parseInt(this.div.style.top);
};

/**
 * Function: installCloseHandler
 *
 * Adds the  as a new image node in  and installs the
 *  event.
 */
mxWindow.prototype.installCloseHandler = function()
{
	this.closeImg = document.createElement('img');
	
	this.closeImg.setAttribute('src', this.closeImage);
	this.closeImg.setAttribute('align', 'right');
	this.closeImg.setAttribute('title', 'Close');
	this.closeImg.style.marginLeft = '2px';
	this.closeImg.style.cursor = 'pointer';
	this.closeImg.style.display = 'none';
	
	this.title.insertBefore(this.closeImg, this.title.firstChild);

	var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
	mxEvent.addListener(this.closeImg, md, mxUtils.bind(this, function(evt)
	{
		this.fireEvent(new mxEventObject(mxEvent.CLOSE, 'event', evt));
		
		if (this.destroyOnClose)
		{
			this.destroy();
		}
		else
		{
			this.setVisible(false);
		}
		
		mxEvent.consume(evt);
	}));
};

/**
 * Function: setImage
 * 
 * Sets the image associated with the window.
 * 
 * Parameters:
 * 
 * image - URL of the image to be used.
 */
mxWindow.prototype.setImage = function(image)
{
	this.image = document.createElement('img');
	this.image.setAttribute('src', image);
	this.image.setAttribute('align', 'left');
	this.image.style.marginRight = '4px';
	this.image.style.marginLeft = '0px';
	this.image.style.marginTop = '-2px';
	
	this.title.insertBefore(this.image, this.title.firstChild);
};

/**
 * Function: setClosable
 * 
 * Sets the image associated with the window.
 * 
 * Parameters:
 * 
 * closable - Boolean specifying if the window should be closable.
 */
mxWindow.prototype.setClosable = function(closable)
{
	this.closeImg.style.display = (closable) ? '' : 'none';
};

/**
 * Function: isVisible
 * 
 * Returns true if the window is visible.
 */
mxWindow.prototype.isVisible = function()
{
	if (this.div != null)
	{
		return this.div.style.visibility != 'hidden';
	}
	
	return false;
};

/**
 * Function: setVisible
 *
 * Shows or hides the window depending on the given flag.
 * 
 * Parameters:
 * 
 * visible - Boolean indicating if the window should be made visible.
 */
mxWindow.prototype.setVisible = function(visible)
{
	if (this.div != null && this.isVisible() != visible)
	{
		if (visible)
		{
			this.show();
		}
		else
		{
			this.hide();
		}
	}
};

/**
 * Function: show
 *
 * Shows the window.
 */
mxWindow.prototype.show = function()
{
	this.div.style.visibility = '';
	this.activate();
	
	var style = mxUtils.getCurrentStyle(this.contentWrapper);
	
	if (!mxClient.IS_IE && (style.overflow == 'auto' || this.resize != null))
	{
		this.contentWrapper.style.height =
			(this.div.offsetHeight - this.title.offsetHeight - 2)+'px';
	}
	
	this.fireEvent(new mxEventObject(mxEvent.SHOW));
};

/**
 * Function: hide
 *
 * Hides the window.
 */
mxWindow.prototype.hide = function()
{
	this.div.style.visibility = 'hidden';
	this.fireEvent(new mxEventObject(mxEvent.HIDE));
};

/**
 * Function: destroy
 *
 * Destroys the window and removes all associated resources. Fires a
 *  event prior to destroying the window.
 */
mxWindow.prototype.destroy = function()
{
	this.fireEvent(new mxEventObject(mxEvent.DESTROY));
	
	if (this.div != null)
	{
		mxEvent.release(this.div);
		this.div.parentNode.removeChild(this.div);
		this.div = null;
	}
	
	this.title = null;
	this.content = null;
	this.contentWrapper = null;
};
/**
 * $Id: mxForm.js,v 1.16 2010-10-08 04:21:45 david Exp $
 * Copyright (c) 2006-2010, Gaudenz Alder, David Benson
 */
/**
 * Class: mxForm
 * 
 * A simple class for creating HTML forms.
 * 
 * Constructor: mxForm
 * 
 * Creates a HTML table using the specified classname.
 */
function mxForm(className)
{
	this.table = document.createElement('table');
	this.table.className = className;
	this.body = document.createElement('tbody');
	
	this.table.appendChild(this.body);
};

/**
 * Variable: table
 * 
 * Holds the DOM node that represents the table.
 */
mxForm.prototype.table = null;

/**
 * Variable: body
 * 
 * Holds the DOM node that represents the tbody (table body). New rows
 * can be added to this object using DOM API.
 */
mxForm.prototype.body = false;

/**
 * Function: getTable
 * 
 * Returns the table that contains this form.
 */
mxForm.prototype.getTable = function()
{
	return this.table;
};

/**
 * Function: addButtons
 * 
 * Helper method to add an OK and Cancel button using the respective
 * functions.
 */
mxForm.prototype.addButtons = function(okFunct, cancelFunct)
{
	var tr = document.createElement('tr');
	var td = document.createElement('td');
	tr.appendChild(td);
	td = document.createElement('td');

	// Adds the ok button
	var button = document.createElement('button');
	mxUtils.write(button, mxResources.get('ok') || 'OK');
	td.appendChild(button);

	mxEvent.addListener(button, 'click', function()
	{
		okFunct();
	});
	
	// Adds the cancel button
	button = document.createElement('button');
	mxUtils.write(button, mxResources.get('cancel') || 'Cancel');
	td.appendChild(button);
	
	mxEvent.addListener(button, 'click', function()
	{
		cancelFunct();
	});
	
	tr.appendChild(td);
	this.body.appendChild(tr);
};

/**
 * Function: addText
 * 
 * Adds a textfield for the given name and value and returns the textfield.
 */
mxForm.prototype.addText = function(name, value)
{
	var input = document.createElement('input');
	
	input.setAttribute('type', 'text');
	input.value = value;
	
	return this.addField(name, input);
};

/**
 * Function: addCheckbox
 * 
 * Adds a checkbox for the given name and value and returns the textfield.
 */
mxForm.prototype.addCheckbox = function(name, value)
{
	var input = document.createElement('input');
	
	input.setAttribute('type', 'checkbox');
	this.addField(name, input);

	// IE can only change the checked value if the input is inside the DOM
	if (value)
	{
		input.checked = true;
	}

	return input;
};

/**
 * Function: addTextarea
 * 
 * Adds a textarea for the given name and value and returns the textarea.
 */
mxForm.prototype.addTextarea = function(name, value, rows)
{
	var input = document.createElement('textarea');
	
	if (mxClient.IS_NS)
	{
		rows--;
	}
	
	input.setAttribute('rows', rows || 2);
	input.value = value;
	
	return this.addField(name, input);
};

/**
 * Function: addCombo
 * 
 * Adds a combo for the given name and returns the combo.
 */
mxForm.prototype.addCombo = function(name, isMultiSelect, size)
{
	var select = document.createElement('select');
	
	if (size != null)
	{
		select.setAttribute('size', size);
	}
	
	if (isMultiSelect)
	{
		select.setAttribute('multiple', 'true');
	}
	
	return this.addField(name, select);
};

/**
 * Function: addOption
 * 
 * Adds an option for the given label to the specified combo.
 */
mxForm.prototype.addOption = function(combo, label, value, isSelected)
{
	var option = document.createElement('option');
	
	mxUtils.writeln(option, label);
	option.setAttribute('value', value);
	
	if (isSelected)
	{
		option.setAttribute('selected', isSelected);
	}
	
	combo.appendChild(option);
};

/**
 * Function: addField
 * 
 * Adds a new row with the name and the input field in two columns and
 * returns the given input.
 */
mxForm.prototype.addField = function(name, input)
{
	var tr = document.createElement('tr');
	var td = document.createElement('td');
	mxUtils.write(td, name);
	tr.appendChild(td);
	
	td = document.createElement('td');
	td.appendChild(input);
	tr.appendChild(td);
	this.body.appendChild(tr);
	
	return input;
};
/**
 * $Id: mxImage.js,v 1.7 2010-01-02 09:45:14 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 * Class: mxImage
 *
 * Encapsulates the URL, width and height of an image.
 * 
 * Constructor: mxImage
 * 
 * Constructs a new image.
 */
function mxImage(src, width, height)
{
	this.src = src;
	this.width = width;
	this.height = height;
};

/**
 * Variable: src
 *
 * String that specifies the URL of the image.
 */
mxImage.prototype.src = null;

/**
 * Variable: width
 *
 * Integer that specifies the width of the image.
 */
mxImage.prototype.width = null;

/**
 * Variable: height
 *
 * Integer that specifies the height of the image.
 */
mxImage.prototype.height = null;
/**
 * $Id: mxDivResizer.js,v 1.22 2010-01-02 09:45:14 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 * Class: mxDivResizer
 * 
 * Maintains the size of a div element in Internet Explorer. This is a
 * workaround for the right and bottom style being ignored in IE.
 * 
 * If you need a div to cover the scrollwidth and -height of a document,
 * then you can use this class as follows:
 * 
 * (code)
 * var resizer = new mxDivResizer(background);
 * resizer.getDocumentHeight = function()
 * {
 *   return document.body.scrollHeight;
 * }
 * resizer.getDocumentWidth = function()
 * {
 *   return document.body.scrollWidth;
 * }
 * resizer.resize();
 * (end)
 * 
 * Constructor: mxDivResizer
 * 
 * Constructs an object that maintains the size of a div
 * element when the window is being resized. This is only
 * required for Internet Explorer as it ignores the respective
 * stylesheet information for DIV elements.
 * 
 * Parameters:
 * 
 * div - Reference to the DOM node whose size should be maintained.
 * container - Optional Container that contains the div. Default is the
 * window.
 */
function mxDivResizer(div, container)
{
	if (div.nodeName.toLowerCase() == 'div')
	{
		if (container == null)
		{
			container = window;
		}

		this.div = div;
		var style = mxUtils.getCurrentStyle(div);
		
		if (style != null)
		{
			this.resizeWidth = style.width == 'auto';
			this.resizeHeight = style.height == 'auto';
		}
		
		mxEvent.addListener(container, 'resize',
			mxUtils.bind(this, function(evt)
			{
				if (!this.handlingResize)
				{
					this.handlingResize = true;
					this.resize();
					this.handlingResize = false;
				}
			})
		);
		
		this.resize();
	}
};

/**
 * Function: resizeWidth
 * 
 * Boolean specifying if the width should be updated.
 */
mxDivResizer.prototype.resizeWidth = true;

/**
 * Function: resizeHeight
 * 
 * Boolean specifying if the height should be updated.
 */
mxDivResizer.prototype.resizeHeight = true;

/**
 * Function: handlingResize
 * 
 * Boolean specifying if the width should be updated.
 */
mxDivResizer.prototype.handlingResize = false;

/**
 * Function: resize
 * 
 * Updates the style of the DIV after the window has been resized.
 */
mxDivResizer.prototype.resize = function()
{
	var w = this.getDocumentWidth();
	var h = this.getDocumentHeight();

	var l = parseInt(this.div.style.left);
	var r = parseInt(this.div.style.right);
	var t = parseInt(this.div.style.top);
	var b = parseInt(this.div.style.bottom);
	
	if (this.resizeWidth &&
		!isNaN(l) &&
		!isNaN(r) &&
		l >= 0 &&
		r >= 0 &&
		w - r - l > 0)
	{
		this.div.style.width = (w - r - l)+'px';
	}
	
	if (this.resizeHeight &&
		!isNaN(t) &&
		!isNaN(b) &&
		t >= 0 &&
		b >= 0 &&
		h - t - b > 0)
	{
		this.div.style.height = (h - t - b)+'px';
	}
};

/**
 * Function: getDocumentWidth
 * 
 * Hook for subclassers to return the width of the document (without
 * scrollbars).
 */
mxDivResizer.prototype.getDocumentWidth = function()
{
	return document.body.clientWidth;
};

/**
 * Function: getDocumentHeight
 * 
 * Hook for subclassers to return the height of the document (without
 * scrollbars).
 */
mxDivResizer.prototype.getDocumentHeight = function()
{
	return document.body.clientHeight;
};
/**
 * $Id: mxDragSource.js,v 1.14 2012-12-05 21:43:16 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 * Class: mxDragSource
 * 
 * Wrapper to create a drag source from a DOM element so that the element can
 * be dragged over a graph and dropped into the graph as a new cell.
 * 
 * TODO: Problem is that in the dropHandler the current preview location is
 * not available, so the preview and the dropHandler must match.
 * 
 * Constructor: mxDragSource
 * 
 * Constructs a new drag source for the given element.
 */
function mxDragSource(element, dropHandler)
{
	this.element = element;
	this.dropHandler = dropHandler;
	
	// Handles a drag gesture on the element
	var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
	mxEvent.addListener(element, md, mxUtils.bind(this, this.mouseDown));
};

/**
 * Variable: element
 *
 * Reference to the DOM node which was made draggable.
 */
mxDragSource.prototype.element = null;

/**
 * Variable: dropHandler
 *
 * Holds the DOM node that is used to represent the drag preview. If this is
 * null then the source element will be cloned and used for the drag preview.
 */
mxDragSource.prototype.dropHandler = null;

/**
 * Variable: dragOffset
 *
 *  that specifies the offset of the . Default is null.
 */
mxDragSource.prototype.dragOffset = null;

/**
 * Variable: dragElement
 *
 * Holds the DOM node that is used to represent the drag preview. If this is
 * null then the source element will be cloned and used for the drag preview.
 */
mxDragSource.prototype.dragElement = null;

/**
 * Variable: previewElement
 *
 * Optional  that specifies the unscaled size of the preview.
 */
mxDragSource.prototype.previewElement = null;

/**
 * Variable: enabled
 *
 * Specifies if this drag source is enabled. Default is true.
 */
mxDragSource.prototype.enabled = true;

/**
 * Variable: currentGraph
 *
 * Reference to the  that is the current drop target.
 */
mxDragSource.prototype.currentGraph = null;

/**
 * Variable: currentDropTarget
 *
 * Holds the current drop target under the mouse.
 */
mxDragSource.prototype.currentDropTarget = null;

/**
 * Variable: currentPoint
 *
 * Holds the current drop location.
 */
mxDragSource.prototype.currentPoint = null;

/**
 * Variable: currentGuide
 *
 * Holds an  for the  if  is not null.
 */
mxDragSource.prototype.currentGuide = null;

/**
 * Variable: currentGuide
 *
 * Holds an  for the  if  is not null.
 */
mxDragSource.prototype.currentHighlight = null;

/**
 * Variable: autoscroll
 *
 * Specifies if the graph should scroll automatically. Default is true.
 */
mxDragSource.prototype.autoscroll = true;

/**
 * Variable: guidesEnabled
 *
 * Specifies if  should be enabled. Default is true.
 */
mxDragSource.prototype.guidesEnabled = true;

/**
 * Variable: gridEnabled
 *
 * Specifies if the grid should be allowed. Default is true.
 */
mxDragSource.prototype.gridEnabled = true;

/**
 * Variable: highlightDropTargets
 *
 * Specifies if drop targets should be highlighted. Default is true.
 */
mxDragSource.prototype.highlightDropTargets = true;

/**
 * Variable: dragElementZIndex
 * 
 * ZIndex for the drag element. Default is 100.
 */
mxDragSource.prototype.dragElementZIndex = 100;

/**
 * Variable: dragElementOpacity
 * 
 * Opacity of the drag element in %. Default is 70.
 */
mxDragSource.prototype.dragElementOpacity = 70;

/**
 * Function: isEnabled
 * 
 * Returns .
 */
mxDragSource.prototype.isEnabled = function()
{
	return this.enabled;
};

/**
 * Function: setEnabled
 * 
 * Sets .
 */
mxDragSource.prototype.setEnabled = function(value)
{
	this.enabled = value;
};

/**
 * Function: isGuidesEnabled
 * 
 * Returns .
 */
mxDragSource.prototype.isGuidesEnabled = function()
{
	return this.guidesEnabled;
};

/**
 * Function: setGuidesEnabled
 * 
 * Sets .
 */
mxDragSource.prototype.setGuidesEnabled = function(value)
{
	this.guidesEnabled = value;
};

/**
 * Function: isGridEnabled
 * 
 * Returns .
 */
mxDragSource.prototype.isGridEnabled = function()
{
	return this.gridEnabled;
};

/**
 * Function: setGridEnabled
 * 
 * Sets .
 */
mxDragSource.prototype.setGridEnabled = function(value)
{
	this.gridEnabled = value;
};

/**
 * Function: getGraphForEvent
 * 
 * Returns the graph for the given mouse event. This implementation returns
 * null.
 */
mxDragSource.prototype.getGraphForEvent = function(evt)
{
	return null;
};

/**
 * Function: getDropTarget
 * 
 * Returns the drop target for the given graph and coordinates. This
 * implementation uses .
 */
mxDragSource.prototype.getDropTarget = function(graph, x, y)
{
	return graph.getCellAt(x, y);
};

/**
 * Function: createDragElement
 * 
 * Creates and returns a clone of the  or the 
 * if the former is not defined.
 */
mxDragSource.prototype.createDragElement = function(evt)
{
	return this.element.cloneNode(true);
};

/**
 * Function: createPreviewElement
 * 
 * Creates and returns an element which can be used as a preview in the given
 * graph.
 */
mxDragSource.prototype.createPreviewElement = function(graph)
{
	return null;
};

/**
 * Function: mouseDown
 * 
 * Returns the drop target for the given graph and coordinates. This
 * implementation uses .
 */
mxDragSource.prototype.mouseDown = function(evt)
{
	if (this.enabled && !mxEvent.isConsumed(evt) && this.mouseMoveHandler == null)
	{
		this.startDrag(evt);
		
		var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
		var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
		
		this.mouseMoveHandler = mxUtils.bind(this, this.mouseMove);
		mxEvent.addListener(document, mm, this.mouseMoveHandler);
		this.mouseUpHandler = mxUtils.bind(this, this.mouseUp);
		mxEvent.addListener(document, mu, this.mouseUpHandler);
		
		// Prevents default action (native DnD for images in FF 10)
		// but does not stop event propagation
		mxEvent.consume(evt, true, false);
	}
};

/**
 * Function: startDrag
 * 
 * Creates the  using .
 */
mxDragSource.prototype.startDrag = function(evt)
{
	this.dragElement = this.createDragElement(evt);
	this.dragElement.style.position = 'absolute';
	this.dragElement.style.zIndex = this.dragElementZIndex;
	mxUtils.setOpacity(this.dragElement, this.dragElementOpacity);
};


/**
 * Function: stopDrag
 * 
 * Removes and destroys the .
 */
mxDragSource.prototype.stopDrag = function(evt)
{
	if (this.dragElement != null)
	{
		if (this.dragElement.parentNode != null)
		{
			this.dragElement.parentNode.removeChild(this.dragElement);
		}
		
		this.dragElement = null;
	}
};

/**
 * Function: graphContainsEvent
 * 
 * Returns true if the given graph contains the given event.
 */
mxDragSource.prototype.graphContainsEvent = function(graph, evt)
{
	var x = mxEvent.getClientX(evt);
	var y = mxEvent.getClientY(evt);
	var offset = mxUtils.getOffset(graph.container);
	var origin = mxUtils.getScrollOrigin();

	// Checks if event is inside the bounds of the graph container
	return x >= offset.x - origin.x && y >= offset.y - origin.y &&
		x <= offset.x - origin.x + graph.container.offsetWidth &&
		y <= offset.y - origin.y + graph.container.offsetHeight;
};

/**
 * Function: mouseMove
 * 
 * Gets the graph for the given event using , updates the
 * , calling  and  on the new and old graph,
 * respectively, and invokes  if  is not null.
 */
mxDragSource.prototype.mouseMove = function(evt)
{
	var graph = this.getGraphForEvent(evt);
	
	// Checks if event is inside the bounds of the graph container
	if (graph != null && !this.graphContainsEvent(graph, evt))
	{
		graph = null;
	}

	if (graph != this.currentGraph)
	{
		if (this.currentGraph != null)
		{
			this.dragExit(this.currentGraph);
		}
		
		this.currentGraph = graph;
		
		if (this.currentGraph != null)
		{
			this.dragEnter(this.currentGraph);
		}
	}
	
	if (this.currentGraph != null)
	{
		this.dragOver(this.currentGraph, evt);
	}

	if (this.dragElement != null && (this.previewElement == null || this.previewElement.style.visibility != 'visible'))
	{
		var x = mxEvent.getClientX(evt);
		var y = mxEvent.getClientY(evt);
		
		if (this.dragElement.parentNode == null)
		{
			document.body.appendChild(this.dragElement);
		}

		this.dragElement.style.visibility = 'visible';
		
		if (this.dragOffset != null)
		{
			x += this.dragOffset.x;
			y += this.dragOffset.y;
		}
		
		x += document.body.scrollLeft || document.documentElement.scrollLeft;
		y += document.body.scrollTop || document.documentElement.scrollTop;
		this.dragElement.style.left = x + 'px';
		this.dragElement.style.top = y + 'px';
	}
	else if (this.dragElement != null)
	{
		this.dragElement.style.visibility = 'hidden';
	}
	
	mxEvent.consume(evt);
};

/**
 * Function: mouseUp
 * 
 * Processes the mouse up event and invokes ,  and 
 * as required.
 */
mxDragSource.prototype.mouseUp = function(evt)
{
	if (this.currentGraph != null)
	{
		if (this.currentPoint != null && (this.previewElement == null ||
			this.previewElement.style.visibility != 'hidden'))
		{
			var scale = this.currentGraph.view.scale;
			var tr = this.currentGraph.view.translate;
			var x = this.currentPoint.x / scale - tr.x;
			var y = this.currentPoint.y / scale - tr.y;
			
			this.drop(this.currentGraph, evt, this.currentDropTarget, x, y);
		}
		
		this.dragExit(this.currentGraph);
	}

	this.stopDrag(evt);
	
	this.currentGraph = null;

	if (this.mouseMoveHandler != null)
	{
		var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
		mxEvent.removeListener(document, mm, this.mouseMoveHandler);
		this.mouseMoveHandler = null;
	}
	
	if (this.mouseUpHandler != null)
	{
		var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
		mxEvent.removeListener(document, mu, this.mouseUpHandler);
		this.mouseUpHandler = null;
	}
		
	mxEvent.consume(evt);
};

/**
 * Function: dragEnter
 * 
 * Actives the given graph as a drop target.
 */
mxDragSource.prototype.dragEnter = function(graph)
{
	graph.isMouseDown = true;
	this.previewElement = this.createPreviewElement(graph);
	
	// Guide is only needed if preview element is used
	if (this.isGuidesEnabled() && this.previewElement != null)
	{
		this.currentGuide = new mxGuide(graph, graph.graphHandler.getGuideStates());
	}
	
	if (this.highlightDropTargets)
	{
		this.currentHighlight = new mxCellHighlight(graph, mxConstants.DROP_TARGET_COLOR);
	}
};

/**
 * Function: dragExit
 * 
 * Deactivates the given graph as a drop target.
 */
mxDragSource.prototype.dragExit = function(graph)
{
	this.currentDropTarget = null;
	this.currentPoint = null;
	graph.isMouseDown = false;
	
	if (this.previewElement != null)
	{
		if (this.previewElement.parentNode != null)
		{
			this.previewElement.parentNode.removeChild(this.previewElement);
		}
		
		this.previewElement = null;
	}
	
	if (this.currentGuide != null)
	{
		this.currentGuide.destroy();
		this.currentGuide = null;
	}
	
	if (this.currentHighlight != null)
	{
		this.currentHighlight.destroy();
		this.currentHighlight = null;
	}
};

/**
 * Function: dragOver
 * 
 * Implements autoscroll, updates the , highlights any drop
 * targets and updates the preview.
 */
mxDragSource.prototype.dragOver = function(graph, evt)
{
	var offset = mxUtils.getOffset(graph.container);
	var origin = mxUtils.getScrollOrigin(graph.container);
	var x = mxEvent.getClientX(evt) - offset.x + origin.x;
	var y = mxEvent.getClientY(evt) - offset.y + origin.y;

	if (graph.autoScroll && (this.autoscroll == null || this.autoscroll))
	{
		graph.scrollPointToVisible(x, y, graph.autoExtend);
	}

	// Highlights the drop target under the mouse
	if (this.currentHighlight != null && graph.isDropEnabled())
	{
		this.currentDropTarget = this.getDropTarget(graph, x, y);
		var state = graph.getView().getState(this.currentDropTarget);
		this.currentHighlight.highlight(state);
	}

	// Updates the location of the preview
	if (this.previewElement != null)
	{
		if (this.previewElement.parentNode == null)
		{
			graph.container.appendChild(this.previewElement);
			
			this.previewElement.style.zIndex = '3';
			this.previewElement.style.position = 'absolute';
		}
		
		var gridEnabled = this.isGridEnabled() && graph.isGridEnabledEvent(evt);
		var hideGuide = true;

		// Grid and guides
		if (this.currentGuide != null && this.currentGuide.isEnabledForEvent(evt))
		{
			// LATER: HTML preview appears smaller than SVG preview
			var w = parseInt(this.previewElement.style.width);
			var h = parseInt(this.previewElement.style.height);
			var bounds = new mxRectangle(0, 0, w, h);
			var delta = new mxPoint(x, y);
			delta = this.currentGuide.move(bounds, delta, gridEnabled);
			hideGuide = false;
			x = delta.x;
			y = delta.y;
		}
		else if (gridEnabled)
		{
			var scale = graph.view.scale;
			var tr = graph.view.translate;
			var off = graph.gridSize / 2;
			x = (graph.snap(x / scale - tr.x - off) + tr.x) * scale;
			y = (graph.snap(y / scale - tr.y - off) + tr.y) * scale;
		}
		
		if (this.currentGuide != null && hideGuide)
		{
			this.currentGuide.hide();
		}
		
		if (this.previewOffset != null)
		{
			x += this.previewOffset.x;
			y += this.previewOffset.y;
		}

		this.previewElement.style.left = Math.round(x) + 'px';
		this.previewElement.style.top = Math.round(y) + 'px';
		this.previewElement.style.visibility = 'visible';
	}
	
	this.currentPoint = new mxPoint(x, y);
};

/**
 * Function: drop
 * 
 * Returns the drop target for the given graph and coordinates. This
 * implementation uses .
 */
mxDragSource.prototype.drop = function(graph, evt, dropTarget, x, y)
{
	this.dropHandler(graph, evt, dropTarget, x, y);
	
	// Had to move this to after the insert because it will
	// affect the scrollbars of the window in IE to try and
	// make the complete container visible.
	// LATER: Should be made optional.
	graph.container.focus();
};
/**
 * $Id: mxToolbar.js,v 1.36 2012-06-22 11:17:13 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 * Class: mxToolbar
 * 
 * Creates a toolbar inside a given DOM node. The toolbar may contain icons,
 * buttons and combo boxes.
 * 
 * Event: mxEvent.SELECT
 * 
 * Fires when an item was selected in the toolbar. The function
 * property contains the function that was selected in .
 * 
 * Constructor: mxToolbar
 * 
 * Constructs a toolbar in the specified container.
 *
 * Parameters:
 *
 * container - DOM node that contains the toolbar.
 */
function mxToolbar(container)
{
	this.container = container;
};

/**
 * Extends mxEventSource.
 */
mxToolbar.prototype = new mxEventSource();
mxToolbar.prototype.constructor = mxToolbar;

/**
 * Variable: container
 * 
 * Reference to the DOM nodes that contains the toolbar.
 */
mxToolbar.prototype.container = null;

/**
 * Variable: enabled
 * 
 * Specifies if events are handled. Default is true.
 */
mxToolbar.prototype.enabled = true;

/**
 * Variable: noReset
 * 
 * Specifies if  requires a forced flag of true for resetting
 * the current mode in the toolbar. Default is false. This is set to true
 * if the toolbar item is double clicked to avoid a reset after a single
 * use of the item.
 */
mxToolbar.prototype.noReset = false;

/**
 * Variable: updateDefaultMode
 * 
 * Boolean indicating if the default mode should be the last selected
 * switch mode or the first inserted switch mode. Default is true, that
 * is the last selected switch mode is the default mode. The default mode
 * is the mode to be selected after a reset of the toolbar. If this is
 * false, then the default mode is the first inserted mode item regardless
 * of what was last selected. Otherwise, the selected item after a reset is
 * the previously selected item.
 */
mxToolbar.prototype.updateDefaultMode = true;

/**
 * Function: addItem
 * 
 * Adds the given function as an image with the specified title and icon
 * and returns the new image node.
 * 
 * Parameters:
 * 
 * title - Optional string that is used as the tooltip.
 * icon - Optional URL of the image to be used. If no URL is given, then a
 * button is created.
 * funct - Function to execute on a mouse click.
 * pressedIcon - Optional URL of the pressed image. Default is a gray
 * background.
 * style - Optional style classname. Default is mxToolbarItem.
 * factoryMethod - Optional factory method for popup menu, eg.
 * function(menu, evt, cell) { menu.addItem('Hello, World!'); }
 */
mxToolbar.prototype.addItem = function(title, icon, funct, pressedIcon, style, factoryMethod)
{
	var img = document.createElement((icon != null) ? 'img' : 'button');
	var initialClassName = style || ((factoryMethod != null) ?
			'mxToolbarMode' : 'mxToolbarItem');
	img.className = initialClassName;
	img.setAttribute('src', icon);
	
	if (title != null)
	{
		if (icon != null)
		{
			img.setAttribute('title', title);
		}
		else
		{
			mxUtils.write(img, title);
		}
	}
	
	this.container.appendChild(img);

	// Invokes the function on a click on the toolbar item
	if (funct != null)
	{
		mxEvent.addListener(img, (mxClient.IS_TOUCH) ? 'touchend' : 'click', funct);
	}
	
	var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
	var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';

	// Highlights the toolbar item with a gray background
	// while it is being clicked with the mouse
	mxEvent.addListener(img, md, mxUtils.bind(this, function(evt)
	{
		if (pressedIcon != null)
		{
			img.setAttribute('src', pressedIcon);
		}
		else
		{
			img.style.backgroundColor = 'gray';
		}
		
		// Popup Menu
		if (factoryMethod != null)
		{
			if (this.menu == null)
			{
				this.menu = new mxPopupMenu();
				this.menu.init();
			}
			
			var last = this.currentImg;
			
			if (this.menu.isMenuShowing())
			{
				this.menu.hideMenu();
			}
			
			if (last != img)
			{
				// Redirects factory method to local factory method
				this.currentImg = img;
				this.menu.factoryMethod = factoryMethod;
				
				var point = new mxPoint(
					img.offsetLeft,
					img.offsetTop + img.offsetHeight);
				this.menu.popup(point.x, point.y, null, evt);

				// Sets and overrides to restore classname
				if (this.menu.isMenuShowing())
				{
					img.className = initialClassName + 'Selected';
					
					this.menu.hideMenu = function()
					{
						mxPopupMenu.prototype.hideMenu.apply(this);
						img.className = initialClassName;
						this.currentImg = null;
					};
				}
			}
		}
	}));

	var mouseHandler = mxUtils.bind(this, function(evt)
	{
		if (pressedIcon != null)
		{
			img.setAttribute('src', icon);
		}
		else
		{
			img.style.backgroundColor = '';
		}
	});
	
	mxEvent.addListener(img, mu, mouseHandler);
	mxEvent.addListener(img, 'mouseout', mouseHandler);
	
	return img;
};

/**
 * Function: addCombo
 * 
 * Adds and returns a new SELECT element using the given style. The element
 * is placed inside a DIV with the mxToolbarComboContainer style classname.
 * 
 * Parameters:
 * 
 * style - Optional style classname. Default is mxToolbarCombo.
 */
mxToolbar.prototype.addCombo = function(style)
{
	var div = document.createElement('div');
	div.style.display = 'inline';
	div.className = 'mxToolbarComboContainer';
	
	var select = document.createElement('select');
	select.className = style || 'mxToolbarCombo';
	div.appendChild(select);
	
	this.container.appendChild(div);
	
	return select;
};

/**
 * Function: addCombo
 * 
 * Adds and returns a new SELECT element using the given title as the
 * default element. The selection is reset to this element after each
 * change.
 * 
 * Parameters:
 * 
 * title - String that specifies the title of the default element.
 * style - Optional style classname. Default is mxToolbarCombo.
 */
mxToolbar.prototype.addActionCombo = function(title, style)
{
	var select = document.createElement('select');
	select.className = style || 'mxToolbarCombo';
	
	this.addOption(select, title, null);
	
	mxEvent.addListener(select, 'change', function(evt)
	{
		var value = select.options[select.selectedIndex];
		select.selectedIndex = 0;
		if (value.funct != null)
		{
			value.funct(evt);
		}
	});
	
	this.container.appendChild(select);
	
	return select;
};

/**
 * Function: addOption
 * 
 * Adds and returns a new OPTION element inside the given SELECT element.
 * If the given value is a function then it is stored in the option's funct
 * field.
 * 
 * Parameters:
 * 
 * combo - SELECT element that will contain the new entry.
 * title - String that specifies the title of the option.
 * value - Specifies the value associated with this option.
 */
mxToolbar.prototype.addOption = function(combo, title, value)
{
	var option = document.createElement('option');
	mxUtils.writeln(option, title);
	
	if (typeof(value) == 'function')
	{
		option.funct = value;
	}
	else
	{
		option.setAttribute('value', value);
	}
	
	combo.appendChild(option);
	
	return option;
};

/**
 * Function: addSwitchMode
 * 
 * Adds a new selectable item to the toolbar. Only one switch mode item may
 * be selected at a time. The currently selected item is the default item
 * after a reset of the toolbar.
 */
mxToolbar.prototype.addSwitchMode = function(title, icon, funct, pressedIcon, style)
{
	var img = document.createElement('img');
	img.initialClassName = style || 'mxToolbarMode';
	img.className = img.initialClassName;
	img.setAttribute('src', icon);
	img.altIcon = pressedIcon;
	
	if (title != null)
	{
		img.setAttribute('title', title);
	}
	
	mxEvent.addListener(img, 'click', mxUtils.bind(this, function(evt)
	{
		var tmp = this.selectedMode.altIcon;
		
		if (tmp != null)
		{
			this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
			this.selectedMode.setAttribute('src', tmp);
		}
		else
		{
			this.selectedMode.className = this.selectedMode.initialClassName;
		}
		
		if (this.updateDefaultMode)
		{
			this.defaultMode = img;
		}
		
		this.selectedMode = img;
		
		var tmp = img.altIcon;
		
		if (tmp != null)
		{
			img.altIcon = img.getAttribute('src');
			img.setAttribute('src', tmp);
		}
		else
		{
			img.className = img.initialClassName+'Selected';
		}
		
		this.fireEvent(new mxEventObject(mxEvent.SELECT));
		funct();
	}));
	
	this.container.appendChild(img);
	
	if (this.defaultMode == null)
	{
		this.defaultMode = img;
		
		// Function should fire only once so
		// do not pass it with the select event
		this.selectMode(img);
		funct();
	}
	
	return img;
};

/**
 * Function: addMode
 * 
 * Adds a new item to the toolbar. The selection is typically reset after
 * the item has been consumed, for example by adding a new vertex to the
 * graph. The reset is not carried out if the item is double clicked.
 * 
 * The function argument uses the following signature: funct(evt, cell) where
 * evt is the native mouse event and cell is the cell under the mouse.
 */
mxToolbar.prototype.addMode = function(title, icon, funct, pressedIcon, style, toggle)
{
	toggle = (toggle != null) ? toggle : true;
	var img = document.createElement((icon != null) ? 'img' : 'button');
	
	img.initialClassName = style || 'mxToolbarMode';
	img.className = img.initialClassName;
	img.setAttribute('src', icon);
	img.altIcon = pressedIcon;

	if (title != null)
	{
		img.setAttribute('title', title);
	}
	
	if (this.enabled && toggle)
	{
		mxEvent.addListener(img, 'click', mxUtils.bind(this, function(evt)
		{
			this.selectMode(img, funct);
			this.noReset = false;
		}));
		mxEvent.addListener(img, 'dblclick',
			mxUtils.bind(this, function(evt)
			{
				this.selectMode(img, funct);
				this.noReset = true;
			})
		);
		
		if (this.defaultMode == null)
		{
			this.defaultMode = img;
			this.defaultFunction = funct;
			this.selectMode(img, funct);
		}
	}

	this.container.appendChild(img);					

	return img;
};

/**
 * Function: selectMode
 * 
 * Resets the state of the previously selected mode and displays the given
 * DOM node as selected. This function fires a select event with the given
 * function as a parameter.
 */
mxToolbar.prototype.selectMode = function(domNode, funct)
{
	if (this.selectedMode != domNode)
	{
		if (this.selectedMode != null)
		{
			var tmp = this.selectedMode.altIcon;
			
			if (tmp != null)
			{
				this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
				this.selectedMode.setAttribute('src', tmp);
			}
			else
			{
				this.selectedMode.className = this.selectedMode.initialClassName;
			}
		}
		
		this.selectedMode = domNode;
		var tmp = this.selectedMode.altIcon;
		
		if (tmp != null)
		{
			this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
			this.selectedMode.setAttribute('src', tmp);
		}
		else
		{
			this.selectedMode.className = this.selectedMode.initialClassName+'Selected';
		}
		
		this.fireEvent(new mxEventObject(mxEvent.SELECT, "function", funct));
	}
};

/**
 * Function: resetMode
 * 
 * Selects the default mode and resets the state of the previously selected
 * mode.
 */
mxToolbar.prototype.resetMode = function(forced)
{
	if ((forced || !this.noReset) &&
		this.selectedMode != this.defaultMode)
	{
		// The last selected switch mode will be activated
		// so the function was already executed and is
		// no longer required here
		this.selectMode(this.defaultMode, this.defaultFunction);
	}
};

/**
 * Function: addSeparator
 * 
 * Adds the specifies image as a separator.
 * 
 * Parameters:
 * 
 * icon - URL of the separator icon.
 */
mxToolbar.prototype.addSeparator = function(icon)
{
	return this.addItem(null, icon, null);
};

/**
 * Function: addBreak
 * 
 * Adds a break to the container.
 */
mxToolbar.prototype.addBreak = function()
{
	mxUtils.br(this.container);
};

/**
 * Function: addLine
 * 
 * Adds a horizontal line to the container.
 */
mxToolbar.prototype.addLine = function()
{
	var hr = document.createElement('hr');
	
	hr.style.marginRight = '6px';
	hr.setAttribute('size', '1');
	
	this.container.appendChild(hr);
};

/**
 * Function: destroy
 * 
 * Removes the toolbar and all its associated resources.
 */
mxToolbar.prototype.destroy = function ()
{
	mxEvent.release(this.container);
	this.container = null;
	this.defaultMode = null;
	this.defaultFunction = null;
	this.selectedMode = null;
	
	if (this.menu != null)
	{
		this.menu.destroy();
	}
};
/**
 * $Id: mxSession.js,v 1.46 2012-08-22 15:30:49 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 * Class: mxSession
 *
 * Session for sharing an  with other parties
 * via a backend that acts as a multicaster for all changes.
 * 
 * Diagram Sharing:
 * 
 * The diagram sharing is a mechanism where each atomic change of the model is
 * encoded into XML using  and then transmitted to the server by the
 *  object. On the server, the XML data is dispatched to each
 * listener on the same diagram (except the sender), and the XML is decoded
 * back into atomic changes on the client side, which are then executed on the
 * model and stored in the command history.
 * 
 * The  specifies how these changes are
 * treated with respect to undo: The default value (true) will undo the last
 * change regardless of whether it was a remote or a local change. If the
 * switch is false, then an undo will go back until the last local change,
 * silently undoing all remote changes up to that point. Note that these
 * changes will be added as new remote changes to the history of the other
 * clients.
 * 
 * Event: mxEvent.CONNECT
 *
 * Fires after the session has been started, that is, after the response to the
 * initial request was received and the session goes into polling mode. This
 * event has no properties.
 *
 * Event: mxEvent.SUSPEND
 *
 * Fires after  was called an the session was not already in suspended
 * state. This event has no properties.
 *
 * Event: mxEvent.RESUME
 *
 * Fires after the session was resumed in . This event has no
 * properties.
 *
 * Event: mxEvent.DISCONNECT
 *
 * Fires after the session was stopped in . The reason
 * property contains the optional exception that was passed to the stop method.
 *
 * Event: mxEvent.NOTIFY
 *
 * Fires after a notification was sent in . The url
 * property contains the URL and the xml property contains the XML
 * data of the request.
 *
 * Event: mxEvent.GET
 *
 * Fires after a response was received in . The url property
 * contains the URL and the request is the  that
 * contains the response.
 *
 * Event: mxEvent.FIRED
 * 
 * Fires after an array of edits has been executed on the model. The
 * changes property contains the array of changes.
 * 
 * Event: mxEvent.RECEIVE
 *
 * Fires after an XML node was received in . The node
 * property contains the node that was received.
 * 
 * Constructor: mxSession
 * 
 * Constructs a new session using the given  and URLs to
 * communicate with the backend.
 * 
 * Parameters:
 * 
 * model -  that contains the data.
 * urlInit - URL to be used for initializing the session.
 * urlPoll - URL to be used for polling the backend.
 * urlNotify - URL to be used for sending changes to the backend.
 */
function mxSession(model, urlInit, urlPoll, urlNotify)
{
	this.model = model;
	this.urlInit = urlInit;
	this.urlPoll = urlPoll;
	this.urlNotify = urlNotify;

	// Resolves cells by id using the model
	if (model != null)
	{
		this.codec = new mxCodec();
		
		this.codec.lookup = function(id)
		{
			return model.getCell(id);
		};
	}
	
	// Adds the listener for notifying the backend of any
	// changes in the model
	model.addListener(mxEvent.NOTIFY, mxUtils.bind(this, function(sender, evt)
	{
		var edit = evt.getProperty('edit');
		
		if (edit != null && this.debug || (this.connected && !this.suspended))
		{
			this.notify(''+this.encodeChanges(edit.changes, edit.undone)+'');
		}
	}));
};

/**
 * Extends mxEventSource.
 */
mxSession.prototype = new mxEventSource();
mxSession.prototype.constructor = mxSession;

/**
 * Variable: model
 * 
 * Reference to the enclosing .
 */
mxSession.prototype.model = null;

/**
 * Variable: urlInit
 * 
 * URL to initialize the session.
 */
mxSession.prototype.urlInit = null;

/**
 * Variable: urlPoll
 * 
 * URL for polling the backend.
 */
mxSession.prototype.urlPoll = null;

/**
 * Variable: urlNotify
 * 
 * URL to send changes to the backend.
 */
mxSession.prototype.urlNotify = null;

/**
 * Variable: codec
 * 
 * Reference to the  used to encoding and decoding changes.
 */
mxSession.prototype.codec = null;

/**
 * Variable: linefeed
 * 
 * Used for encoding linefeeds. Default is '
'.
 */
mxSession.prototype.linefeed = '
';

/**
 * Variable: escapePostData
 * 
 * Specifies if the data in the post request sent in 
 * should be converted using encodeURIComponent. Default is true.
 */
mxSession.prototype.escapePostData = true;

/**
 * Variable: significantRemoteChanges
 * 
 * Whether remote changes should be significant in the
 * local command history. Default is true.
 */
mxSession.prototype.significantRemoteChanges = true;

/**
 * Variable: sent
 * 
 * Total number of sent bytes.
 */
mxSession.prototype.sent = 0;

/**
 * Variable: received
 * 
 * Total number of received bytes.
 */
mxSession.prototype.received = 0;

/**
 * Variable: debug
 * 
 * Specifies if the session should run in debug mode. In this mode, no
 * connection is established. The data is written to the console instead.
 * Default is false.
 */
mxSession.prototype.debug = false;

/**
 * Variable: connected
 */
mxSession.prototype.connected = false;
	
/**
 * Variable: send
 */
mxSession.prototype.suspended = false;
	
/**
 * Variable: polling
 */
mxSession.prototype.polling = false;

/**
 * Function: start
 */
mxSession.prototype.start = function()
{
	if (this.debug)
	{
		this.connected = true;
		this.fireEvent(new mxEventObject(mxEvent.CONNECT));
	}
	else if (!this.connected)
	{
		this.get(this.urlInit, mxUtils.bind(this, function(req)
		{
			this.connected = true;
			this.fireEvent(new mxEventObject(mxEvent.CONNECT));
			this.poll();
		}));
	}
};

/**
 * Function: suspend
 * 
 * Suspends the polling. Use  to reactive the session. Fires a
 * suspend event.
 */
mxSession.prototype.suspend = function()
{
	if (this.connected && !this.suspended)
	{
		this.suspended = true;
		this.fireEvent(new mxEventObject(mxEvent.SUSPEND));
	}
};
	
/**
 * Function: resume
 * 
 * Resumes the session if it has been suspended. Fires a resume-event
 * before starting the polling.
 */
mxSession.prototype.resume = function(type, attr, value)
{
	if (this.connected &&
		this.suspended)
	{
		this.suspended = false;
		this.fireEvent(new mxEventObject(mxEvent.RESUME));
		
		if (!this.polling)
		{
			this.poll();
		}
	}
};
		
/**
 * Function: stop
 * 
 * Stops the session and fires a disconnect event. The given reason is
 * passed to the disconnect event listener as the second argument.
 */
mxSession.prototype.stop = function(reason)
{
	if (this.connected)
	{
		this.connected = false;
	}
	
	this.fireEvent(new mxEventObject(mxEvent.DISCONNECT,
			'reason', reason));
};

/**
 * Function: poll
 * 
 * Sends an asynchronous GET request to .
 */
mxSession.prototype.poll = function()
{
	if (this.connected &&
		!this.suspended &&
		this.urlPoll != null)
	{
		this.polling = true;

		this.get(this.urlPoll, mxUtils.bind(this, function()
		{
			this.poll();
		}));
	}
	else
	{
		this.polling = false;
	}
};

/**
 * Function: notify
 * 
 * Sends out the specified XML to  and fires a  event.
 */
mxSession.prototype.notify = function(xml, onLoad, onError)
{
	if (xml != null &&
		xml.length > 0)
	{
		if (this.urlNotify != null)
		{
			if (this.debug)
			{
				mxLog.show();
				mxLog.debug('mxSession.notify: '+this.urlNotify+' xml='+xml);			
			}
			else
			{
				xml = ''+xml+'';
				
				if (this.escapePostData)
				{
					xml = encodeURIComponent(xml);
				}
				
				mxUtils.post(this.urlNotify, 'xml='+xml, onLoad, onError);
			}
		}
		
		this.sent += xml.length;
		this.fireEvent(new mxEventObject(mxEvent.NOTIFY,
				'url', this.urlNotify, 'xml', xml));
	}
};

/**
 * Function: get
 * 
 * Sends an asynchronous get request to the given URL, fires a  event
 * and invokes the given onLoad function when a response is received.
 */
mxSession.prototype.get = function(url, onLoad, onError)
{
	// Response after browser refresh has no global scope
	// defined. This response is ignored and the session
	// stops implicitely.
	if (typeof(mxUtils) != 'undefined')
	{
		var onErrorWrapper = mxUtils.bind(this, function(ex)
		{
			if (onError != null)
			{
				onError(ex);
			}
			else
			{
				this.stop(ex);
			}
		});

		// Handles a successful response for
		// the above request.
		mxUtils.get(url, mxUtils.bind(this, function(req)
		{
			if (typeof(mxUtils) != 'undefined')
			{
    			if (req.isReady() && req.getStatus() != 404)
    			{
    				this.received += req.getText().length;
					this.fireEvent(new mxEventObject(mxEvent.GET, 'url', url, 'request', req));

					if (this.isValidResponse(req))
					{
		    			if (req.getText().length > 0)
		    			{
							var node = req.getDocumentElement();
							
							if (node == null)
							{
								onErrorWrapper('Invalid response: '+req.getText());
							}
							else
							{
								this.receive(node);
							}
						}
		    			
		    			if (onLoad != null)
		    			{
							onLoad(req);
						}
					}
				}
				else
				{
					onErrorWrapper('Response not ready');
				}
			}
		}),
		// Handles a transmission error for the
		// above request
		function(req)
		{
			onErrorWrapper('Transmission error');
		});
	}
};

/**
 * Function: isValidResponse
 * 
 * Returns true if the response data in the given  is valid.
 */
mxSession.prototype.isValidResponse = function(req)
{
	// TODO: Find condition to check if response
	// contains valid XML (not eg. the PHP code).
	return req.getText().indexOf('= 0 && i < changes.length; i += step)
	{	
		// Newlines must be kept, they will be converted
		// to 
 when the server sends data to the
		// client
		var node = this.codec.encode(changes[i]);
		xml += mxUtils.getXml(node, this.linefeed);
	}
	
	return xml;
};

/**
 * Function: receive
 * 
 * Processes the given node by applying the changes to the model. If the nodename
 * is state, then the namespace is used as a prefix for creating Ids in the model,
 * and the child nodes are visited recursively. If the nodename is delta, then the
 * changes encoded in the child nodes are applied to the model. Each call to the
 * receive function fires a  event with the given node as the second argument
 * after processing. If changes are processed, then the function additionally fires
 * a  event before the  event.
 */
mxSession.prototype.receive = function(node)
{
	if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT)
	{
		// Uses the namespace in the model
		var ns = node.getAttribute('namespace');
		
		if (ns != null)
		{
			this.model.prefix = ns + '-';
		}
		
		var child = node.firstChild;
		
		while (child != null)
		{
			var name = child.nodeName.toLowerCase();
			
			if (name == 'state')
			{
				this.processState(child);
			}
			else if (name == 'delta')
			{
				this.processDelta(child);	
			}
			
			child = child.nextSibling;
		}
		
		// Fires receive event
		this.fireEvent(new mxEventObject(mxEvent.RECEIVE, 'node', node));
	}
};

/**
 * Function: processState
 * 
 * Processes the given state node which contains the current state of the
 * remote model.
 */
mxSession.prototype.processState = function(node)
{
	var dec = new mxCodec(node.ownerDocument);
	dec.decode(node.firstChild, this.model);
};

/**
 * Function: processDelta
 * 
 * Processes the given delta node which contains a sequence of edits which in
 * turn map to one transaction on the remote model each.
 */
mxSession.prototype.processDelta = function(node)
{
	var edit = node.firstChild;
	
	while (edit != null)
	{
		if (edit.nodeName == 'edit')
		{
			this.processEdit(edit);
		}
		
		edit = edit.nextSibling;
	}
};

/**
 * Function: processEdit
 * 
 * Processes the given edit by executing its changes and firing the required
 * events via the model.
 */
mxSession.prototype.processEdit = function(node)
{
	var changes = this.decodeChanges(node);
	
	if (changes.length > 0)
	{
		var edit = this.createUndoableEdit(changes);
		
		// No notify event here to avoid the edit from being encoded and transmitted
		// LATER: Remove changes property (deprecated)
		this.model.fireEvent(new mxEventObject(mxEvent.CHANGE,
			'edit', edit, 'changes', changes));
		this.model.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
		this.fireEvent(new mxEventObject(mxEvent.FIRED, 'edit', edit));
	}
};

/**
 * Function: createUndoableEdit
 * 
 * Creates a new  that implements the notify function to fire a
 *  and  event via the model.
 */
mxSession.prototype.createUndoableEdit = function(changes)
{
	var edit = new mxUndoableEdit(this.model, this.significantRemoteChanges);
	edit.changes = changes;
	
	edit.notify = function()
	{
		// LATER: Remove changes property (deprecated)
		edit.source.fireEvent(new mxEventObject(mxEvent.CHANGE,
			'edit', edit, 'changes', edit.changes));
		edit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,
			'edit', edit, 'changes', edit.changes));
	};
	
	return edit;
};

/**
 * Function: decodeChanges
 * 
 * Decodes and executes the changes represented by the children in the
 * given node. Returns an array that contains all changes.
 */
mxSession.prototype.decodeChanges = function(node)
{
	// Updates the document in the existing codec
	this.codec.document = node.ownerDocument;

	// Parses and executes the changes on the model
	var changes = [];
	node = node.firstChild;
	
	while (node != null)
	{
		var change = this.decodeChange(node);
		
		if (change != null)
		{
			changes.push(change);
		}
		
		node = node.nextSibling;
	}
	
	return changes;
};

/**
 * Function: decodeChange
 * 
 * Decodes, executes and returns the change object represented by the given
 * XML node.
 */
mxSession.prototype.decodeChange = function(node)
{
	var change = null;

	if (node.nodeType == mxConstants.NODETYPE_ELEMENT)
	{
		if (node.nodeName == 'mxRootChange')
		{
			// Handles the special case were no ids should be
			// resolved in the existing model. This change will
			// replace all registered ids and cells from the
			// model and insert a new cell hierarchy instead.
			var tmp = new mxCodec(node.ownerDocument);
			change = tmp.decode(node);
		}
		else
		{
			change = this.codec.decode(node);
		}
		
		if (change != null)
		{
			change.model = this.model;
			change.execute();
			
			// Workaround for references not being resolved if cells have
			// been removed from the model prior to being referenced. This
			// adds removed cells in the codec object lookup table.
			if (node.nodeName == 'mxChildChange' && change.parent == null)
			{
				this.cellRemoved(change.child);
			}
		}
	}
	
	return change;
};

/**
 * Function: cellRemoved
 * 
 * Adds removed cells to the codec object lookup for references to the removed
 * cells after this point in time.
 */
mxSession.prototype.cellRemoved = function(cell, codec)
{
	this.codec.putObject(cell.getId(), cell);
	
	var childCount = this.model.getChildCount(cell);
	
	for (var i = 0; i < childCount; i++)
	{
		this.cellRemoved(this.model.getChildAt(cell, i));
	}
};
/**
 * $Id: mxUndoableEdit.js,v 1.14 2010-09-15 16:58:51 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 * Class: mxUndoableEdit
 * 
 * Implements a composite undoable edit.
 * 
 * Constructor: mxUndoableEdit
 * 
 * Constructs a new undoable edit for the given source.
 */
function mxUndoableEdit(source, significant)
{
	this.source = source;
	this.changes = [];
	this.significant = (significant != null) ? significant : true;
};

/**
 * Variable: source
 * 
 * Specifies the source of the edit.
 */
mxUndoableEdit.prototype.source = null;

/**
 * Variable: changes
 * 
 * Array that contains the changes that make up this edit. The changes are
 * expected to either have an undo and redo function, or an execute
 * function. Default is an empty array.
 */
mxUndoableEdit.prototype.changes = null;

/**
 * Variable: significant
 * 
 * Specifies if the undoable change is significant.
 * Default is true.
 */
mxUndoableEdit.prototype.significant = null;

/**
 * Variable: undone
 * 
 * Specifies if this edit has been undone. Default is false.
 */
mxUndoableEdit.prototype.undone = false;

/**
 * Variable: redone
 * 
 * Specifies if this edit has been redone. Default is false.
 */
mxUndoableEdit.prototype.redone = false;

/**
 * Function: isEmpty
 * 
 * Returns true if the this edit contains no changes.
 */
mxUndoableEdit.prototype.isEmpty = function()
{
	return this.changes.length == 0;
};

/**
 * Function: isSignificant
 * 
 * Returns .
 */
mxUndoableEdit.prototype.isSignificant = function()
{
	return this.significant;
};

/**
 * Function: add
 * 
 * Adds the specified change to this edit. The change is an object that is
 * expected to either have an undo and redo, or an execute function.
 */
mxUndoableEdit.prototype.add = function(change)
{
	this.changes.push(change);
};

/**
 * Function: notify
 * 
 * Hook to notify any listeners of the changes after an  or 
 * has been carried out. This implementation is empty.
 */
mxUndoableEdit.prototype.notify = function() { };

/**
 * Function: die
 * 
 * Hook to free resources after the edit has been removed from the command
 * history. This implementation is empty.
 */
mxUndoableEdit.prototype.die = function() { };

/**
 * Function: undo
 * 
 * Undoes all changes in this edit.
 */
mxUndoableEdit.prototype.undo = function()
{
	if (!this.undone)
	{
		var count = this.changes.length;
		
		for (var i = count - 1; i >= 0; i--)
		{
			var change = this.changes[i];
			
			if (change.execute != null)
			{
				change.execute();
			}
			else if (change.undo != null)
			{
				change.undo();
			}
		}
		
		this.undone = true;
		this.redone = false;
	}
	
	this.notify();
};

/**
 * Function: redo
 * 
 * Redoes all changes in this edit.
 */
mxUndoableEdit.prototype.redo = function()
{
	if (!this.redone)
	{
		var count = this.changes.length;
		
		for (var i = 0; i < count; i++)
		{
			var change = this.changes[i];
			
			if (change.execute != null)
			{
				change.execute();
			}
			else if (change.redo != null)
			{
				change.redo();
			}
		}
		
		this.undone = false;
		this.redone = true;
	}
	
	this.notify();
};
/**
 * $Id: mxUndoManager.js,v 1.30 2011-10-05 06:39:19 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 * Class: mxUndoManager
 *
 * Implements a command history. When changing the graph model, an
 *  object is created at the start of the transaction (when
 * model.beginUpdate is called). All atomic changes are then added to this
 * object until the last model.endUpdate call, at which point the
 *  is dispatched in an event, and added to the history inside
 * . This is done by an event listener in
 * .
 * 
 * Each atomic change of the model is represented by an object (eg.
 * , ,  etc) which contains the
 * complete undo information. The  also listens to the
 *  and stores it's changes to the current root as insignificant
 * undoable changes, so that drilling (step into, step up) is undone.
 * 
 * This means when you execute an atomic change on the model, then change the
 * current root on the view and click undo, the change of the root will be
 * undone together with the change of the model so that the display represents
 * the state at which the model was changed. However, these changes are not
 * transmitted for sharing as they do not represent a state change.
 *
 * Example:
 * 
 * When adding an undo manager to a graph, make sure to add it
 * to the model and the view as well to maintain a consistent
 * display across multiple undo/redo steps.
 *
 * (code)
 * var undoManager = new mxUndoManager();
 * var listener = function(sender, evt)
 * {
 *   undoManager.undoableEditHappened(evt.getProperty('edit'));
 * };
 * graph.getModel().addListener(mxEvent.UNDO, listener);
 * graph.getView().addListener(mxEvent.UNDO, listener);
 * (end)
 * 
 * The code creates a function that informs the undoManager
 * of an undoable edit and binds it to the undo event of
 *  and  using
 * .
 * 
 * Event: mxEvent.CLEAR
 * 
 * Fires after  was invoked. This event has no properties.
 * 
 * Event: mxEvent.UNDO
 * 
 * Fires afer a significant edit was undone in . The edit
 * property contains the  that was undone.
 * 
 * Event: mxEvent.REDO
 * 
 * Fires afer a significant edit was redone in . The edit
 * property contains the  that was redone.
 * 
 * Event: mxEvent.ADD
 * 
 * Fires after an undoable edit was added to the history. The edit
 * property contains the  that was added.
 * 
 * Constructor: mxUndoManager
 *
 * Constructs a new undo manager with the given history size. If no history
 * size is given, then a default size of 100 steps is used.
 */
function mxUndoManager(size)
{
	this.size = (size != null) ? size : 100;
	this.clear();
};

/**
 * Extends mxEventSource.
 */
mxUndoManager.prototype = new mxEventSource();
mxUndoManager.prototype.constructor = mxUndoManager;

/**
 * Variable: size
 * 
 * Maximum command history size. 0 means unlimited history. Default is
 * 100.
 */
mxUndoManager.prototype.size = null;

/**
 * Variable: history
 * 
 * Array that contains the steps of the command history.
 */
mxUndoManager.prototype.history = null;

/**
 * Variable: indexOfNextAdd
 * 
 * Index of the element to be added next.
 */
mxUndoManager.prototype.indexOfNextAdd = 0;

/**
 * Function: isEmpty
 * 
 * Returns true if the history is empty.
 */
mxUndoManager.prototype.isEmpty = function()
{
	return this.history.length == 0;
};

/**
 * Function: clear
 * 
 * Clears the command history.
 */
mxUndoManager.prototype.clear = function()
{
	this.history = [];
	this.indexOfNextAdd = 0;
	this.fireEvent(new mxEventObject(mxEvent.CLEAR));
};

/**
 * Function: canUndo
 * 
 * Returns true if an undo is possible.
 */
mxUndoManager.prototype.canUndo = function()
{
	return this.indexOfNextAdd > 0;
};

/**
 * Function: undo
 * 
 * Undoes the last change.
 */
mxUndoManager.prototype.undo = function()
{
    while (this.indexOfNextAdd > 0)
    {
        var edit = this.history[--this.indexOfNextAdd];
        edit.undo();

		if (edit.isSignificant())
        {
        	this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
            break;
        }
    }
};

/**
 * Function: canRedo
 * 
 * Returns true if a redo is possible.
 */
mxUndoManager.prototype.canRedo = function()
{
	return this.indexOfNextAdd < this.history.length;
};

/**
 * Function: redo
 * 
 * Redoes the last change.
 */
mxUndoManager.prototype.redo = function()
{
    var n = this.history.length;
    
    while (this.indexOfNextAdd < n)
    {
        var edit =  this.history[this.indexOfNextAdd++];
        edit.redo();
        
        if (edit.isSignificant())
        {
        	this.fireEvent(new mxEventObject(mxEvent.REDO, 'edit', edit));
            break;
        }
    }
};

/**
 * Function: undoableEditHappened
 * 
 * Method to be called to add new undoable edits to the .
 */
mxUndoManager.prototype.undoableEditHappened = function(undoableEdit)
{
	this.trim();
	
	if (this.size > 0 &&
		this.size == this.history.length)
	{
		this.history.shift();
	}
	
	this.history.push(undoableEdit);
	this.indexOfNextAdd = this.history.length;
	this.fireEvent(new mxEventObject(mxEvent.ADD, 'edit', undoableEdit));
};

/**
 * Function: trim
 * 
 * Removes all pending steps after  from the history,
 * invoking die on each edit. This is called from .
 */
mxUndoManager.prototype.trim = function()
{
	if (this.history.length > this.indexOfNextAdd)
	{
		var edits = this.history.splice(this.indexOfNextAdd,
			this.history.length - this.indexOfNextAdd);
			
		for (var i = 0; i < edits.length; i++)
		{
			edits[i].die();
		}
	}
};
/**
 * $Id: mxUrlConverter.js,v 1.3 2012-08-24 17:10:41 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 *
 * Class: mxUrlConverter
 * 
 * Converts relative and absolute URLs to absolute URLs with protocol and domain.
 */
var mxUrlConverter = function(root)
{
	/**
	 * Variable: enabled
	 * 
	 * Specifies if the converter is enabled. Default is true.
	 */
	var enabled = true;

	/**
	 * Variable: baseUrl
	 * 
	 * Specifies the base URL to be used as a prefix for relative URLs.
	 */
	var baseUrl = null;

	/**
	 * Variable: baseDomain
	 * 
	 * Specifies the base domain to be used as a prefix for absolute URLs.
	 */
	var baseDomain = null;
	
	// Private helper function to update the base URL
	var updateBaseUrl = function()
	{
		baseDomain = location.protocol + '//' + location.host;
		baseUrl = baseDomain + location.pathname;
		var tmp = baseUrl.lastIndexOf('/');
		
		// Strips filename etc
		if (tmp > 0)
		{
			baseUrl = baseUrl.substring(0, tmp + 1);
		}
	};

	// Returns public interface
	return {

		/**
		 * Function: isEnabled
		 * 
		 * Returns .
		 */
		isEnabled: function()
		{
			return enabled;
		},

		/**
		 * Function: setEnabled
		 * 
		 * Sets .
		 */
		setEnabled: function(value)
		{
			enabled = value;
		},

		/**
		 * Function: getBaseUrl
		 * 
		 * Returns .
		 */
		getBaseUrl: function()
		{
			return baseUrl;
		},

		/**
		 * Function: setBaseUrl
		 * 
		 * Sets .
		 */
		setBaseUrl: function(value)
		{
			baseUrl = value;
		},

		/**
		 * Function: getBaseDomain
		 * 
		 * Returns .
		 */
		getBaseDomain: function()
		{
			return baseUrl;
		},

		/**
		 * Function: setBaseDomain
		 * 
		 * Sets .
		 */
		setBaseDomain: function(value)
		{
			baseUrl = value;
		},

		/**
		 * Function: convert
		 * 
		 * Converts the given URL to an absolute URL with protol and domain.
		 * Relative URLs are first converted to absolute URLs.
		 */
		convert: function(url)
		{
			if (enabled && url.indexOf('http://') != 0 && url.indexOf('https://') != 0 && url.indexOf('data:image') != 0)
			{
				if (baseUrl == null)
				{
					updateBaseUrl();
				}
				
				if (url.charAt(0) == '/')
				{
					url = baseDomain + url;
				}
				else
				{
					url = baseUrl + url;
				}
			}
			
			return url;
		}

	};

};/**
 * $Id: mxPanningManager.js,v 1.7 2012-06-13 06:46:37 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 * Class: mxPanningManager
 *
 * Implements a handler for panning.
 */
function mxPanningManager(graph)
{
	this.thread = null;
	this.active = false;
	this.tdx = 0;
	this.tdy = 0;
	this.t0x = 0;
	this.t0y = 0;
	this.dx = 0;
	this.dy = 0;
	this.scrollbars = false;
	this.scrollLeft = 0;
	this.scrollTop = 0;
	
	this.mouseListener =
	{
	    mouseDown: function(sender, me) { },
	    mouseMove: function(sender, me) { },
	    mouseUp: mxUtils.bind(this, function(sender, me)
	    {
	    	if (this.active)
	    	{
	    		this.stop();
	    	}
	    })
	};
	
	graph.addMouseListener(this.mouseListener);
	
	// Stops scrolling on every mouseup anywhere in the document
	mxEvent.addListener(document, 'mouseup', mxUtils.bind(this, function()
	{
    	if (this.active)
    	{
    		this.stop();
    	}
	}));
	
	var createThread = mxUtils.bind(this, function()
	{
    	this.scrollbars = mxUtils.hasScrollbars(graph.container);
    	this.scrollLeft = graph.container.scrollLeft;
    	this.scrollTop = graph.container.scrollTop;

    	return window.setInterval(mxUtils.bind(this, function()
		{
			this.tdx -= this.dx;
			this.tdy -= this.dy;

			if (this.scrollbars)
			{
				var left = -graph.container.scrollLeft - Math.ceil(this.dx);
				var top = -graph.container.scrollTop - Math.ceil(this.dy);
				graph.panGraph(left, top);
				graph.panDx = this.scrollLeft - graph.container.scrollLeft;
				graph.panDy = this.scrollTop - graph.container.scrollTop;
				graph.fireEvent(new mxEventObject(mxEvent.PAN));
				// TODO: Implement graph.autoExtend
			}
			else
			{
				graph.panGraph(this.getDx(), this.getDy());
			}
		}), this.delay);
	});
	
	this.isActive = function()
	{
		return active;
	};
	
	this.getDx = function()
	{
		return Math.round(this.tdx);
	};
	
	this.getDy = function()
	{
		return Math.round(this.tdy);
	};
	
	this.start = function()
	{
		this.t0x = graph.view.translate.x;
		this.t0y = graph.view.translate.y;
		this.active = true;
	};
	
	this.panTo = function(x, y, w, h)
	{
		if (!this.active)
		{
			this.start();
		}
		
    	this.scrollLeft = graph.container.scrollLeft;
    	this.scrollTop = graph.container.scrollTop;
		
		w = (w != null) ? w : 0;
		h = (h != null) ? h : 0;
		
		var c = graph.container;
		this.dx = x + w - c.scrollLeft - c.clientWidth;
		
		if (this.dx < 0 && Math.abs(this.dx) < this.border)
		{
			this.dx = this.border + this.dx;
		}
		else if (this.handleMouseOut)
		{
			this.dx = Math.max(this.dx, 0);
		}
		else
		{
			this.dx = 0;
		}
		
		if (this.dx == 0)
		{
			this.dx = x - c.scrollLeft;
			
			if (this.dx > 0 && this.dx < this.border)
			{
				this.dx = this.dx - this.border;
			}
			else if (this.handleMouseOut)
			{
				this.dx = Math.min(0, this.dx);
			}
			else
			{
				this.dx = 0;
			}
		}
		
		this.dy = y + h - c.scrollTop - c.clientHeight;

		if (this.dy < 0 && Math.abs(this.dy) < this.border)
		{
			this.dy = this.border + this.dy;
		}
		else if (this.handleMouseOut)
		{
			this.dy = Math.max(this.dy, 0);
		}
		else
		{
			this.dy = 0;
		}
		
		if (this.dy == 0)
		{
			this.dy = y - c.scrollTop;
			
			if (this.dy > 0 && this.dy < this.border)
			{
				this.dy = this.dy - this.border;
			}
			else if (this.handleMouseOut)
			{
				this.dy = Math.min(0, this.dy);
			} 
			else
			{
				this.dy = 0;
			}
		}
		
		if (this.dx != 0 || this.dy != 0)
		{
			this.dx *= this.damper;
			this.dy *= this.damper;
			
			if (this.thread == null)
			{
				this.thread = createThread();
			}
		}
		else if (this.thread != null)
		{
			window.clearInterval(this.thread);
			this.thread = null;
		}
	};
	
	this.stop = function()
	{
		if (this.active)
		{
			this.active = false;
		
			if (this.thread != null)
	    	{
				window.clearInterval(this.thread);
				this.thread = null;
	    	}
			
			this.tdx = 0;
			this.tdy = 0;
			
			if (!this.scrollbars)
			{
				var px = graph.panDx;
				var py = graph.panDy;
		    	
		    	if (px != 0 || py != 0)
		    	{
		    		graph.panGraph(0, 0);
			    	graph.view.setTranslate(this.t0x + px / graph.view.scale, this.t0y + py / graph.view.scale);
		    	}
			}
			else
			{
				graph.panDx = 0;
				graph.panDy = 0;
				graph.fireEvent(new mxEventObject(mxEvent.PAN));
			}
		}
	};
	
	this.destroy = function()
	{
		graph.removeMouseListener(this.mouseListener);
	};
};

/**
 * Variable: damper
 * 
 * Damper value for the panning. Default is 1/6.
 */
mxPanningManager.prototype.damper = 1/6;

/**
 * Variable: delay
 * 
 * Delay in milliseconds for the panning. Default is 10.
 */
mxPanningManager.prototype.delay = 10;

/**
 * Variable: handleMouseOut
 * 
 * Specifies if mouse events outside of the component should be handled. Default is true. 
 */
mxPanningManager.prototype.handleMouseOut = true;

/**
 * Variable: border
 * 
 * Border to handle automatic panning inside the component. Default is 0 (disabled).
 */
mxPanningManager.prototype.border = 0;
/**
 * $Id: mxPath.js,v 1.24 2012-06-13 17:31:32 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 * Class: mxPath
 *
 * An abstraction for creating VML and SVG paths. See  for using this
 * object inside an  for painting cells.
 * 
 * Constructor: mxPath
 *
 * Constructs a path for the given format, which is one of svg or vml.
 * 
 * Parameters:
 * 
 * format - String specifying the . May be one of vml or svg
 * (default).
 */
function mxPath(format)
{
	this.format = format;
	this.path = [];
	this.translate = new mxPoint(0, 0);
};

/**
 * Variable: format
 *
 * Defines the format for the output of this path. Possible values are
 * svg and vml.
 */
mxPath.prototype.format = null;

/**
 * Variable: translate
 *
 *  that specifies the translation of the complete path.
 */
mxPath.prototype.translate = null;

/**
 * Variable: scale
 *
 * Number that specifies the translation of the path.
 */
mxPath.prototype.scale = 1;

/**
 * Variable: path
 *
 * Contains the textual representation of the path as an array.
 */
mxPath.prototype.path = null;

/**
 * Function: isVml
 *
 * Returns true if  is vml.
 */
mxPath.prototype.isVml = function()
{
	return this.format == 'vml';
};

/**
 * Function: getPath
 *
 * Returns string that represents the path in .
 */
mxPath.prototype.getPath = function()
{
	return this.path.join('');
};

/**
 * Function: setTranslate
 *
 * Set the global translation of this path, that is, the origin of the 
 * coordinate system.
 * 
 * Parameters:
 * 
 * x - X-coordinate of the new origin.
 * y - Y-coordinate of the new origin.
 */
mxPath.prototype.setTranslate = function(x, y)
{
	this.translate = new mxPoint(x, y);
};

/**
 * Function: moveTo
 *
 * Moves the cursor to (x, y).
 * 
 * Parameters:
 * 
 * x - X-coordinate of the new cursor location.
 * y - Y-coordinate of the new cursor location.
 */
mxPath.prototype.moveTo = function(x, y)
{
	x += this.translate.x;
	y += this.translate.y;
	
	x *= this.scale;
	y *= this.scale;
	
	if (this.isVml())
	{
		this.path.push('m ', Math.round(x), ' ', Math.round(y), ' ');
	}
	else
	{
		this.path.push('M ', x, ' ', y, ' ');
	}
};
	
/**
 * Function: lineTo
 *
 * Draws a straight line from the current poin to (x, y).
 * 
 * Parameters:
 * 
 * x - X-coordinate of the endpoint.
 * y - Y-coordinate of the endpoint.
 */
mxPath.prototype.lineTo = function(x, y)
{
	x += this.translate.x;
	y += this.translate.y;
	
	x *= this.scale;
	y *= this.scale;
	
	if (this.isVml())
	{
		this.path.push('l ', Math.round(x), ' ', Math.round(y), ' ');
	}
	else
	{
		this.path.push('L ', x, ' ', y, ' ');
	}
};

/**
 * Function: quadTo
 * 
 * Draws a quadratic Bézier curve from the current point to (x, y) using
 * (x1, y1) as the control point.
 * 
 * Parameters:
 * 
 * x1 - X-coordinate of the control point.
 * y1 - Y-coordinate of the control point.
 * x - X-coordinate of the endpoint. 
 * y - Y-coordinate of the endpoint.
 */
mxPath.prototype.quadTo = function(x1, y1, x, y)
{
	x1 += this.translate.x;
	y1 += this.translate.y;
	
	x1 *= this.scale;
	y1 *= this.scale;
	
	x += this.translate.x;
	y += this.translate.y;
	
	x *= this.scale;
	y *= this.scale;
	
	if (this.isVml())
	{
		this.path.push('c ', Math.round(x1), ' ', Math.round(y1), ' ', Math.round(x), ' ',
			Math.round(y), ' ', Math.round(x), ' ', Math.round(y), ' ');
	}
	else
	{
		this.path.push('Q ', x1, ' ', y1, ' ', x, ' ', y, ' ');
	}
};

/**
 * Function: curveTo
 *
 * Draws a cubic Bézier curve from the current point to (x, y) using
 * (x1, y1) as the control point at the beginning of the curve and (x2, y2)
 * as the control point at the end of the curve.
 * 
 * Parameters:
 * 
 * x1 - X-coordinate of the first control point.
 * y1 - Y-coordinate of the first control point.
 * x2 - X-coordinate of the second control point.
 * y2 - Y-coordinate of the second control point.
 * x - X-coordinate of the endpoint. 
 * y - Y-coordinate of the endpoint.
 */
mxPath.prototype.curveTo = function(x1, y1, x2, y2, x, y)
{
	x1 += this.translate.x;
	y1 += this.translate.y;
	
	x1 *= this.scale;
	y1 *= this.scale;
	
	x2 += this.translate.x;
	y2 += this.translate.y;
	
	x2 *= this.scale;
	y2 *= this.scale;
	
	x += this.translate.x;
	y += this.translate.y;
	
	x *= this.scale;
	y *= this.scale;
	
	if (this.isVml())
	{
		this.path.push('c ', Math.round(x1), ' ', Math.round(y1), ' ', Math.round(x2),
			' ', Math.round(y2), ' ', Math.round(x), ' ', Math.round(y), ' ');
	}
	else
	{
		this.path.push('C ', x1, ' ', y1, ' ', x2,
			' ', y2, ' ', x, ' ', y, ' ');
	}
};

/**
 * Function: ellipse
 *
 * Adds the given ellipse. Some implementations may require the path to be
 * closed after this operation.
 */
mxPath.prototype.ellipse = function(x, y, w, h)
{
	x += this.translate.x;
	y += this.translate.y;
	x *= this.scale;
	y *= this.scale;

	if (this.isVml())
	{
		this.path.push('at ', Math.round(x), ' ', Math.round(y), ' ', Math.round(x + w), ' ', Math.round(y +  h), ' ',
				Math.round(x), ' ', Math.round(y + h / 2), ' ', Math.round(x), ' ', Math.round(y + h / 2));
	}
	else
	{
		var startX = x;
		var startY = y + h/2;
		var endX = x + w;
		var endY = y + h/2;
		var r1 = w/2;
		var r2 = h/2;
		this.path.push('M ', startX, ' ', startY, ' ');
		this.path.push('A ', r1, ' ', r2, ' 0 1 0 ', endX, ' ', endY, ' ');
		this.path.push('A ', r1, ' ', r2, ' 0 1 0 ', startX, ' ', startY);
	}
};

/**
 * Function: addPath
 *
 * Adds the given path.
 */
mxPath.prototype.addPath = function(path)
{
	this.path = this.path.concat(path.path);
};

/**
 * Function: write
 *
 * Writes directly into the path. This bypasses all conversions.
 */
mxPath.prototype.write = function(string)
{
	this.path.push(string, ' ');
};

/**
 * Function: end
 *
 * Ends the path.
 */
mxPath.prototype.end = function()
{
	if (this.format == 'vml')
	{
		this.path.push('e');
	}
};

/**
 * Function: close
 *
 * Closes the path.
 */
mxPath.prototype.close = function()
{
	if (this.format == 'vml')
	{
		this.path.push('x e');
	}
	else
	{
		this.path.push('Z');
	}
};
/**
 * $Id: mxPopupMenu.js,v 1.37 2012-04-22 10:16:23 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 * Class: mxPopupMenu
 * 
 * Event handler that pans and creates popupmenus. To use the left
 * mousebutton for panning without interfering with cell moving and
 * resizing, use  and . For grid size
 * steps while panning, use . This handler is built-into
 *  and enabled using .
 * 
 * Constructor: mxPopupMenu
 * 
 * Constructs an event handler that creates a popupmenu. The
 * event handler is not installed anywhere in this ctor.
 * 
 * Event: mxEvent.SHOW
 *
 * Fires after the menu has been shown in .
 */
function mxPopupMenu(factoryMethod)
{
	this.factoryMethod = factoryMethod;
	
	if (factoryMethod != null)
	{
		this.init();
	}
};

/**
 * Extends mxEventSource.
 */
mxPopupMenu.prototype = new mxEventSource();
mxPopupMenu.prototype.constructor = mxPopupMenu;

/**
 * Variable: submenuImage
 * 
 * URL of the image to be used for the submenu icon.
 */
mxPopupMenu.prototype.submenuImage = mxClient.imageBasePath + '/submenu.gif';

/**
 * Variable: zIndex
 * 
 * Specifies the zIndex for the popupmenu and its shadow. Default is 1006.
 */
mxPopupMenu.prototype.zIndex = 10006;

/**
 * Variable: factoryMethod
 * 
 * Function that is used to create the popup menu. The function takes the
 * current panning handler, the  under the mouse and the mouse
 * event that triggered the call as arguments.
 */
mxPopupMenu.prototype.factoryMethod = null;

/**
 * Variable: useLeftButtonForPopup
 * 
 * Specifies if popupmenus should be activated by clicking the left mouse
 * button. Default is false.
 */
mxPopupMenu.prototype.useLeftButtonForPopup = false;

/**
 * Variable: enabled
 * 
 * Specifies if events are handled. Default is true.
 */
mxPopupMenu.prototype.enabled = true;

/**
 * Variable: itemCount
 * 
 * Contains the number of times  has been called for a new menu.
 */
mxPopupMenu.prototype.itemCount = 0;

/**
 * Variable: autoExpand
 * 
 * Specifies if submenus should be expanded on mouseover. Default is false.
 */
mxPopupMenu.prototype.autoExpand = false;

/**
 * Variable: smartSeparators
 * 
 * Specifies if separators should only be added if a menu item follows them.
 * Default is false.
 */
mxPopupMenu.prototype.smartSeparators = false;

/**
 * Variable: labels
 * 
 * Specifies if any labels should be visible. Default is true.
 */
mxPopupMenu.prototype.labels = true;

/**
 * Function: init
 * 
 * Initializes the shapes required for this vertex handler.
 */
mxPopupMenu.prototype.init = function()
{
	// Adds the inner table
	this.table = document.createElement('table');
	this.table.className = 'mxPopupMenu';
	
	this.tbody = document.createElement('tbody');
	this.table.appendChild(this.tbody);

	// Adds the outer div
	this.div = document.createElement('div');
	this.div.className = 'mxPopupMenu';
	this.div.style.display = 'inline';
	this.div.style.zIndex = this.zIndex;
	this.div.appendChild(this.table);

	// Disables the context menu on the outer div
	mxEvent.disableContextMenu(this.div);
};

/**
 * Function: isEnabled
 * 
 * Returns true if events are handled. This implementation
 * returns .
 */
mxPopupMenu.prototype.isEnabled = function()
{
	return this.enabled;
};
	
/**
 * Function: setEnabled
 * 
 * Enables or disables event handling. This implementation
 * updates .
 */
mxPopupMenu.prototype.setEnabled = function(enabled)
{
	this.enabled = enabled;
};

/**
 * Function: isPopupTrigger
 * 
 * Returns true if the given event is a popupmenu trigger for the optional
 * given cell.
 * 
 * Parameters:
 * 
 * me -  that represents the mouse event.
 */
mxPopupMenu.prototype.isPopupTrigger = function(me)
{
	return me.isPopupTrigger() || (this.useLeftButtonForPopup &&
		mxEvent.isLeftMouseButton(me.getEvent()));
};

/**
 * Function: addItem
 * 
 * Adds the given item to the given parent item. If no parent item is specified
 * then the item is added to the top-level menu. The return value may be used
 * as the parent argument, ie. as a submenu item. The return value is the table
 * row that represents the item.
 * 
 * Paramters:
 * 
 * title - String that represents the title of the menu item.
 * image - Optional URL for the image icon.
 * funct - Function associated that takes a mouseup or touchend event.
 * parent - Optional item returned by .
 * iconCls - Optional string that represents the CSS class for the image icon.
 * IconsCls is ignored if image is given.
 * enabled - Optional boolean indicating if the item is enabled. Default is true.
 */
mxPopupMenu.prototype.addItem = function(title, image, funct, parent, iconCls, enabled)
{
	parent = parent || this;
	this.itemCount++;
	
	// Smart separators only added if element contains items
	if (parent.willAddSeparator)
	{
		if (parent.containsItems)
		{
			this.addSeparator(parent, true);
		}

		parent.willAddSeparator = false;
	}

	parent.containsItems = true;
	var tr = document.createElement('tr');
	tr.className = 'mxPopupMenuItem';
	var col1 = document.createElement('td');
	col1.className = 'mxPopupMenuIcon';
	
	// Adds the given image into the first column
	if (image != null)
	{
		var img = document.createElement('img');
		img.src = image;
		col1.appendChild(img);
	}
	else if (iconCls != null)
	{
		var div = document.createElement('div');
		div.className = iconCls;
		col1.appendChild(div);
	}
	
	tr.appendChild(col1);
	
	if (this.labels)
	{
		var col2 = document.createElement('td');
		col2.className = 'mxPopupMenuItem' +
			((enabled != null && !enabled) ? ' disabled' : '');
		mxUtils.write(col2, title);
		col2.align = 'left';
		tr.appendChild(col2);
	
		var col3 = document.createElement('td');
		col3.className = 'mxPopupMenuItem' +
			((enabled != null && !enabled) ? ' disabled' : '');
		col3.style.paddingRight = '6px';
		col3.style.textAlign = 'right';
		
		tr.appendChild(col3);
		
		if (parent.div == null)
		{
			this.createSubmenu(parent);
		}
	}
	
	parent.tbody.appendChild(tr);

	if (enabled == null || enabled)
	{
		var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
		var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
		var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
		
		// Consumes the event on mouse down
		mxEvent.addListener(tr, md, mxUtils.bind(this, function(evt)
		{
			this.eventReceiver = tr;
			
			if (parent.activeRow != tr && parent.activeRow != parent)
			{
				if (parent.activeRow != null &&
					parent.activeRow.div.parentNode != null)
				{
					this.hideSubmenu(parent);
				}
				
				if (tr.div != null)
				{
					this.showSubmenu(parent, tr);
					parent.activeRow = tr;
				}
			}
			
			mxEvent.consume(evt);
		}));
		
		mxEvent.addListener(tr, mm, mxUtils.bind(this, function(evt)
		{
			if (parent.activeRow != tr && parent.activeRow != parent)
			{
				if (parent.activeRow != null &&
					parent.activeRow.div.parentNode != null)
				{
					this.hideSubmenu(parent);
				}
				
				if (this.autoExpand && tr.div != null)
				{
					this.showSubmenu(parent, tr);
					parent.activeRow = tr;
				}
			}
	
			// Sets hover style because TR in IE doesn't have hover
			tr.className = 'mxPopupMenuItemHover';
		}));
	
		mxEvent.addListener(tr, mu, mxUtils.bind(this, function(evt)
		{
			// EventReceiver avoids clicks on a submenu item
			// which has just been shown in the mousedown
			if (this.eventReceiver == tr)
			{
				if (parent.activeRow != tr)
				{
					this.hideMenu();
				}
				
				if (funct != null)
				{
					funct(evt);
				}
			}
			
			this.eventReceiver = null;
			mxEvent.consume(evt);
		}));
	
		// Resets hover style because TR in IE doesn't have hover
		mxEvent.addListener(tr, 'mouseout',
			mxUtils.bind(this, function(evt)
			{
				tr.className = 'mxPopupMenuItem';
			})
		);
	}
	
	return tr;
};

/**
 * Function: createSubmenu
 * 
 * Creates the nodes required to add submenu items inside the given parent
 * item. This is called in  if a parent item is used for the first
 * time. This adds various DOM nodes and a  to the parent.
 * 
 * Parameters:
 * 
 * parent - An item returned by .
 */
mxPopupMenu.prototype.createSubmenu = function(parent)
{
	parent.table = document.createElement('table');
	parent.table.className = 'mxPopupMenu';

	parent.tbody = document.createElement('tbody');
	parent.table.appendChild(parent.tbody);

	parent.div = document.createElement('div');
	parent.div.className = 'mxPopupMenu';

	parent.div.style.position = 'absolute';
	parent.div.style.display = 'inline';
	parent.div.style.zIndex = this.zIndex;
	
	parent.div.appendChild(parent.table);
	
	var img = document.createElement('img');
	img.setAttribute('src', this.submenuImage);
	
	// Last column of the submenu item in the parent menu
	td = parent.firstChild.nextSibling.nextSibling;
	td.appendChild(img);
};

/**
 * Function: showSubmenu
 * 
 * Shows the submenu inside the given parent row.
 */
mxPopupMenu.prototype.showSubmenu = function(parent, row)
{
	if (row.div != null)
	{
		row.div.style.left = (parent.div.offsetLeft +
			row.offsetLeft+row.offsetWidth - 1) + 'px';
		row.div.style.top = (parent.div.offsetTop+row.offsetTop) + 'px';
		document.body.appendChild(row.div);
		
		// Moves the submenu to the left side if there is no space
		var left = parseInt(row.div.offsetLeft);
		var width = parseInt(row.div.offsetWidth);
		
		var b = document.body;
		var d = document.documentElement;
		
		var right = (b.scrollLeft || d.scrollLeft) + (b.clientWidth || d.clientWidth);
		
		if (left + width > right)
		{
			row.div.style.left = (parent.div.offsetLeft - width +
				((mxClient.IS_IE) ? 6 : -6)) + 'px';
		}
		
		mxUtils.fit(row.div);
	}
};

/**
 * Function: addSeparator
 * 
 * Adds a horizontal separator in the given parent item or the top-level menu
 * if no parent is specified.
 * 
 * Parameters:
 * 
 * parent - Optional item returned by .
 * force - Optional boolean to ignore . Default is false.
 */
mxPopupMenu.prototype.addSeparator = function(parent, force)
{
	parent = parent || this;
	
	if (this.smartSeparators && !force)
	{
		parent.willAddSeparator = true;
	}
	else if (parent.tbody != null)
	{
		parent.willAddSeparator = false;
		var tr = document.createElement('tr');
		
		var col1 = document.createElement('td');
		col1.className = 'mxPopupMenuIcon';
		col1.style.padding = '0 0 0 0px';
		
		tr.appendChild(col1);
		
		var col2 = document.createElement('td');
		col2.style.padding = '0 0 0 0px';
		col2.setAttribute('colSpan', '2');
	
		var hr = document.createElement('hr');
		hr.setAttribute('size', '1');
		col2.appendChild(hr);
		
		tr.appendChild(col2);
		
		parent.tbody.appendChild(tr);
	}
};

/**
 * Function: popup
 * 
 * Shows the popup menu for the given event and cell.
 * 
 * Example:
 * 
 * (code)
 * graph.panningHandler.popup = function(x, y, cell, evt)
 * {
 *   mxUtils.alert('Hello, World!');
 * }
 * (end)
 */
mxPopupMenu.prototype.popup = function(x, y, cell, evt)
{
	if (this.div != null && this.tbody != null && this.factoryMethod != null)
	{
		this.div.style.left = x + 'px';
		this.div.style.top = y + 'px';
		
		// Removes all child nodes from the existing menu
		while (this.tbody.firstChild != null)
		{
			mxEvent.release(this.tbody.firstChild);
			this.tbody.removeChild(this.tbody.firstChild);
		}
		
		this.itemCount = 0;
		this.factoryMethod(this, cell, evt);
		
		if (this.itemCount > 0)
		{
			this.showMenu();
			this.fireEvent(new mxEventObject(mxEvent.SHOW));
		}
	}
};

/**
 * Function: isMenuShowing
 * 
 * Returns true if the menu is showing.
 */
mxPopupMenu.prototype.isMenuShowing = function()
{
	return this.div != null && this.div.parentNode == document.body;
};

/**
 * Function: showMenu
 * 
 * Shows the menu.
 */
mxPopupMenu.prototype.showMenu = function()
{
	// Disables filter-based shadow in IE9 standards mode
	if (document.documentMode >= 9)
	{
		this.div.style.filter = 'none';
	}
	
	// Fits the div inside the viewport
	document.body.appendChild(this.div);
	mxUtils.fit(this.div);
};

/**
 * Function: hideMenu
 * 
 * Removes the menu and all submenus.
 */
mxPopupMenu.prototype.hideMenu = function()
{
	if (this.div != null)
	{
		if (this.div.parentNode != null)
		{
			this.div.parentNode.removeChild(this.div);
		}
		
		this.hideSubmenu(this);
		this.containsItems = false;
	}
};

/**
 * Function: hideSubmenu
 * 
 * Removes all submenus inside the given parent.
 * 
 * Parameters:
 * 
 * parent - An item returned by .
 */
mxPopupMenu.prototype.hideSubmenu = function(parent)
{
	if (parent.activeRow != null)
	{
		this.hideSubmenu(parent.activeRow);
		
		if (parent.activeRow.div.parentNode != null)
		{
			parent.activeRow.div.parentNode.removeChild(parent.activeRow.div);
		}
		
		parent.activeRow = null;
	}
};

/**
 * Function: destroy
 * 
 * Destroys the handler and all its resources and DOM nodes.
 */
mxPopupMenu.prototype.destroy = function()
{
	if (this.div != null)
	{
		mxEvent.release(this.div);
		
		if (this.div.parentNode != null)
		{
			this.div.parentNode.removeChild(this.div);
		}
		
		this.div = null;
	}
};
/**
 * $Id: mxAutoSaveManager.js,v 1.9 2010-09-16 09:10:21 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 * Class: mxAutoSaveManager
 * 
 * Manager for automatically saving diagrams. The  hook must be
 * implemented.
 * 
 * Example:
 * 
 * (code)
 * var mgr = new mxAutoSaveManager(editor.graph);
 * mgr.save = function()
 * {
 *   mxLog.show();
 *   mxLog.debug('save');
 * };
 * (end)
 * 
 * Constructor: mxAutoSaveManager
 *
 * Constructs a new automatic layout for the given graph.
 *
 * Arguments:
 * 
 * graph - Reference to the enclosing graph. 
 */
function mxAutoSaveManager(graph)
{
	// Notifies the manager of a change
	this.changeHandler = mxUtils.bind(this, function(sender, evt)
	{
		if (this.isEnabled())
		{
			this.graphModelChanged(evt.getProperty('edit').changes);
		}
	});

	this.setGraph(graph);
};

/**
 * Extends mxEventSource.
 */
mxAutoSaveManager.prototype = new mxEventSource();
mxAutoSaveManager.prototype.constructor = mxAutoSaveManager;

/**
 * Variable: graph
 * 
 * Reference to the enclosing .
 */
mxAutoSaveManager.prototype.graph = null;

/**
 * Variable: autoSaveDelay
 * 
 * Minimum amount of seconds between two consecutive autosaves. Eg. a
 * value of 1 (s) means the graph is not stored more than once per second.
 * Default is 10.
 */
mxAutoSaveManager.prototype.autoSaveDelay = 10;

/**
 * Variable: autoSaveThrottle
 * 
 * Minimum amount of seconds between two consecutive autosaves triggered by
 * more than  changes within a timespan of less than
 *  seconds. Eg. a value of 1 (s) means the graph is not
 * stored more than once per second even if there are more than
 *  changes within that timespan. Default is 2.
 */
mxAutoSaveManager.prototype.autoSaveThrottle = 2;

/**
 * Variable: autoSaveThreshold
 * 
 * Minimum amount of ignored changes before an autosave. Eg. a value of 2
 * means after 2 change of the graph model the autosave will trigger if the
 * condition below is true. Default is 5.
 */
mxAutoSaveManager.prototype.autoSaveThreshold = 5;

/**
 * Variable: ignoredChanges
 * 
 * Counter for ignored changes in autosave.
 */
mxAutoSaveManager.prototype.ignoredChanges = 0;

/**
 * Variable: lastSnapshot
 * 
 * Used for autosaving. See .
 */
mxAutoSaveManager.prototype.lastSnapshot = 0;

/**
 * Variable: enabled
 * 
 * Specifies if event handling is enabled. Default is true.
 */
mxAutoSaveManager.prototype.enabled = true;

/**
 * Variable: changeHandler
 * 
 * Holds the function that handles graph model changes.
 */
mxAutoSaveManager.prototype.changeHandler = null;

/**
 * Function: isEnabled
 * 
 * Returns true if events are handled. This implementation
 * returns .
 */
mxAutoSaveManager.prototype.isEnabled = function()
{
	return this.enabled;
};

/**
 * Function: setEnabled
 * 
 * Enables or disables event handling. This implementation
 * updates .
 * 
 * Parameters:
 * 
 * enabled - Boolean that specifies the new enabled state.
 */
mxAutoSaveManager.prototype.setEnabled = function(value)
{
	this.enabled = value;
};

/**
 * Function: setGraph
 * 
 * Sets the graph that the layouts operate on.
 */
mxAutoSaveManager.prototype.setGraph = function(graph)
{
	if (this.graph != null)
	{
		this.graph.getModel().removeListener(this.changeHandler);
	}
	
	this.graph = graph;
	
	if (this.graph != null)
	{
		this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
	}
};

/**
 * Function: save
 * 
 * Empty hook that is called if the graph should be saved.
 */
mxAutoSaveManager.prototype.save = function()
{
	// empty
};

/**
 * Function: graphModelChanged
 * 
 * Invoked when the graph model has changed.
 */
mxAutoSaveManager.prototype.graphModelChanged = function(changes)
{
	var now = new Date().getTime();
	var dt = (now - this.lastSnapshot) / 1000;
	
	if (dt > this.autoSaveDelay ||
		(this.ignoredChanges >= this.autoSaveThreshold &&
		 dt > this.autoSaveThrottle))
	{
		this.save();
		this.reset();
	}
	else
	{
		// Increments the number of ignored changes
		this.ignoredChanges++;
	}
};

/**
 * Function: reset
 * 
 * Resets all counters.
 */
mxAutoSaveManager.prototype.reset = function()
{
	this.lastSnapshot = new Date().getTime();
	this.ignoredChanges = 0;
};

/**
 * Function: destroy
 * 
 * Removes all handlers from the  and deletes the reference to it.
 */
mxAutoSaveManager.prototype.destroy = function()
{
	this.setGraph(null);
};
/**
 * $Id: mxAnimation.js,v 1.2 2010-03-19 12:53:29 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 *
 * Class: mxAnimation
 * 
 * Implements a basic animation in JavaScript.
 * 
 * Constructor: mxAnimation
 * 
 * Constructs an animation.
 * 
 * Parameters:
 * 
 * graph - Reference to the enclosing .
 */
function mxAnimation(delay)
{
	this.delay = (delay != null) ? delay : 20;
};

/**
 * Extends mxEventSource.
 */
mxAnimation.prototype = new mxEventSource();
mxAnimation.prototype.constructor = mxAnimation;

/**
 * Variable: delay
 * 
 * Specifies the delay between the animation steps. Defaul is 30ms.
 */
mxAnimation.prototype.delay = null;

/**
 * Variable: thread
 * 
 * Reference to the thread while the animation is running.
 */
mxAnimation.prototype.thread = null;

/**
 * Function: startAnimation
 *
 * Starts the animation by repeatedly invoking updateAnimation.
 */
mxAnimation.prototype.startAnimation = function()
{
	if (this.thread == null)
	{
		this.thread = window.setInterval(mxUtils.bind(this, this.updateAnimation), this.delay);
	}
};

/**
 * Function: updateAnimation
 *
 * Hook for subclassers to implement the animation. Invoke stopAnimation
 * when finished, startAnimation to resume. This is called whenever the
 * timer fires and fires an mxEvent.EXECUTE event with no properties.
 */
mxAnimation.prototype.updateAnimation = function()
{
	this.fireEvent(new mxEventObject(mxEvent.EXECUTE));
};

/**
 * Function: stopAnimation
 *
 * Stops the animation by deleting the timer and fires an .
 */
mxAnimation.prototype.stopAnimation = function()
{
	if (this.thread != null)
	{
		window.clearInterval(this.thread);
		this.thread = null;
		this.fireEvent(new mxEventObject(mxEvent.DONE));
	}
};
/**
 * $Id: mxMorphing.js,v 1.4 2010-06-03 13:37:07 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 *
 * Class: mxMorphing
 * 
 * Implements animation for morphing cells. Here is an example of
 * using this class for animating the result of a layout algorithm:
 * 
 * (code)
 * graph.getModel().beginUpdate();
 * try
 * {
 *   var circleLayout = new mxCircleLayout(graph);
 *   circleLayout.execute(graph.getDefaultParent());
 * }
 * finally
 * {
 *   var morph = new mxMorphing(graph);
 *   morph.addListener(mxEvent.DONE, function()
 *   {
 *     graph.getModel().endUpdate();
 *   });
 *   
 *   morph.startAnimation();
 * }
 * (end)
 * 
 * Constructor: mxMorphing
 * 
 * Constructs an animation.
 * 
 * Parameters:
 * 
 * graph - Reference to the enclosing .
 * steps - Optional number of steps in the morphing animation. Default is 6.
 * ease - Optional easing constant for the animation. Default is 1.5.
 * delay - Optional delay between the animation steps. Passed to .
 */
function mxMorphing(graph, steps, ease, delay)
{
	mxAnimation.call(this, delay);
	this.graph = graph;
	this.steps = (steps != null) ? steps : 6;
	this.ease = (ease != null) ? ease : 1.5;
};

/**
 * Extends mxEventSource.
 */
mxMorphing.prototype = new mxAnimation();
mxMorphing.prototype.constructor = mxMorphing;

/**
 * Variable: graph
 * 
 * Specifies the delay between the animation steps. Defaul is 30ms.
 */
mxMorphing.prototype.graph = null;

/**
 * Variable: steps
 * 
 * Specifies the maximum number of steps for the morphing.
 */
mxMorphing.prototype.steps = null;

/**
 * Variable: step
 * 
 * Contains the current step.
 */
mxMorphing.prototype.step = 0;

/**
 * Variable: ease
 * 
 * Ease-off for movement towards the given vector. Larger values are
 * slower and smoother. Default is 4.
 */
mxMorphing.prototype.ease = null;

/**
 * Variable: cells
 * 
 * Optional array of cells to be animated. If this is not specified
 * then all cells are checked and animated if they have been moved
 * in the current transaction.
 */
mxMorphing.prototype.cells = null;

/**
 * Function: updateAnimation
 *
 * Animation step.
 */
mxMorphing.prototype.updateAnimation = function()
{
	var move = new mxCellStatePreview(this.graph);

	if (this.cells != null)
	{
		// Animates the given cells individually without recursion
		for (var i = 0; i < this.cells.length; i++)
		{
			this.animateCell(cells[i], move, false);
		}
	}
	else
	{
		// Animates all changed cells by using recursion to find
		// the changed cells but not for the animation itself
		this.animateCell(this.graph.getModel().getRoot(), move, true);
	}
	
	this.show(move);
	
	if (move.isEmpty() ||
		this.step++ >= this.steps)
	{
		this.stopAnimation();
	}
};

/**
 * Function: show
 *
 * Shows the changes in the given .
 */
mxMorphing.prototype.show = function(move)
{
	move.show();
};

/**
 * Function: animateCell
 *
 * Animates the given cell state using .
 */
mxMorphing.prototype.animateCell = function(cell, move, recurse)
{
	var state = this.graph.getView().getState(cell);
	var delta = null;

	if (state != null)
	{
		// Moves the animated state from where it will be after the model
		// change by subtracting the given delta vector from that location
		delta = this.getDelta(state);

		if (this.graph.getModel().isVertex(cell) &&
			(delta.x != 0 || delta.y != 0))
		{
			var translate = this.graph.view.getTranslate();
			var scale = this.graph.view.getScale();
			
			delta.x += translate.x * scale;
			delta.y += translate.y * scale;
			
			move.moveState(state, -delta.x / this.ease, -delta.y / this.ease);
		}
	}
	
	if (recurse && !this.stopRecursion(state, delta))
	{
		var childCount = this.graph.getModel().getChildCount(cell);

		for (var i = 0; i < childCount; i++)
		{
			this.animateCell(this.graph.getModel().getChildAt(cell, i), move, recurse);
		}
	}
};

/**
 * Function: stopRecursion
 *
 * Returns true if the animation should not recursively find more
 * deltas for children if the given parent state has been animated.
 */
mxMorphing.prototype.stopRecursion = function(state, delta)
{
	return delta != null && (delta.x != 0 || delta.y != 0);
};

/**
 * Function: getDelta
 *
 * Returns the vector between the current rendered state and the future
 * location of the state after the display will be updated.
 */
mxMorphing.prototype.getDelta = function(state)
{
	var origin = this.getOriginForCell(state.cell);
	var translate = this.graph.getView().getTranslate();
	var scale = this.graph.getView().getScale();
	var current = new mxPoint(
		state.x / scale - translate.x,
		state.y / scale - translate.y);

	return new mxPoint(
		(origin.x - current.x) * scale,
		(origin.y - current.y) * scale);
};

/**
 * Function: getOriginForCell
 *
 * Returns the top, left corner of the given cell. TODO: Improve performance
 * by using caching inside this method as the result per cell never changes
 * during the lifecycle of this object.
 */
mxMorphing.prototype.getOriginForCell = function(cell)
{
	var result = null;
	
	if (cell != null)
	{
		result = this.getOriginForCell(this.graph.getModel().getParent(cell));
		var geo = this.graph.getCellGeometry(cell);
		
		// TODO: Handle offset, relative geometries etc
		if (geo != null)
		{
			result.x += geo.x;
			result.y += geo.y;
		}
	}
	
	if (result == null)
	{
		var t = this.graph.view.getTranslate();
		result = new mxPoint(-t.x, -t.y);
	}
	
	return result;
};
/**
 * $Id: mxImageBundle.js,v 1.3 2011-01-20 19:08:11 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 * Class: mxImageBundle
 *
 * Maps from keys to base64 encoded images or file locations. All values must
 * be URLs or use the format data:image/format followed by a comma and the base64
 * encoded image data, eg. "data:image/gif,XYZ", where XYZ is the base64 encoded
 * image data.
 * 
 * To add a new image bundle to an existing graph, the following code is used:
 * 
 * (code)
 * var bundle = new mxImageBundle(alt);
 * bundle.putImage('myImage', 'data:image/gif,R0lGODlhEAAQAMIGAAAAAICAAICAgP' +
 *   '//AOzp2O3r2////////yH+FUNyZWF0ZWQgd2l0aCBUaGUgR0lNUAAh+QQBCgAHACwAAAAA' +
 *   'EAAQAAADTXi63AowynnAMDfjPUDlnAAJhmeBFxAEloliKltWmiYCQvfVr6lBPB1ggxN1hi' +
 *   'laSSASFQpIV5HJBDyHpqK2ejVRm2AAgZCdmCGO9CIBADs=', fallback);
 * graph.addImageBundle(bundle);
 * (end);
 * 
 * Alt is an optional boolean (default is false) that specifies if the value
 * or the fallback should be returned in .
 * 
 * The image can then be referenced in any cell style using image=myImage.
 * If you are using mxOutline, you should use the same image bundles in the
 * graph that renders the outline.
 * 
 * The keys for images are resolved in  and
 * turned into a data URI if the returned value has a short data URI format
 * as specified above.
 * 
 * A typical value for the fallback is a MTHML link as defined in RFC 2557.
 * Note that this format requires a file to be dynamically created on the
 * server-side, or the page that contains the graph to be modified to contain
 * the resources, this can be done by adding a comment that contains the
 * resource in the HEAD section of the page after the title tag.
 * 
 * This type of fallback mechanism should be used in IE6 and IE7. IE8 does
 * support data URIs, but the maximum size is limited to 32 KB, which means
 * all data URIs should be limited to 32 KB.
 */
function mxImageBundle(alt)
{
	this.images = [];
	this.alt = (alt != null) ? alt : false;
};

/**
 * Variable: images
 * 
 * Maps from keys to images.
 */
mxImageBundle.prototype.images = null;

/**
 * Variable: alt
 * 
 * Specifies if the fallback representation should be returned.
 */
mxImageBundle.prototype.images = null;

/**
 * Function: putImage
 * 
 * Adds the specified entry to the map. The entry is an object with a value and
 * fallback property as specified in the arguments.
 */
mxImageBundle.prototype.putImage = function(key, value, fallback)
{
	this.images[key] = {value: value, fallback: fallback};
};

/**
 * Function: getImage
 * 
 * Returns the value for the given key. This returns the value
 * or fallback, depending on . The fallback is returned if
 *  is true, the value is returned otherwise.
 */
mxImageBundle.prototype.getImage = function(key)
{
	var result = null;
	
	if (key != null)
	{
		var img = this.images[key];
		
		if (img != null)
		{
			result = (this.alt) ? img.fallback : img.value;
		}
	}
	
	return result;
};
/**
 * $Id: mxImageExport.js,v 1.48 2013-01-16 08:40:17 gaudenz Exp $
 * Copyright (c) 2006-2010, JGraph Ltd
 */
/**
 * Class: mxImageExport
 * 
 * Creates a new image export instance to be used with an export canvas. Here
 * is an example that uses this class to create an image via a backend using
 * .
 * 
 * (code)
 * var xmlDoc = mxUtils.createXmlDocument();
 * var root = xmlDoc.createElement('output');
 * xmlDoc.appendChild(root);
 * 
 * var xmlCanvas = new mxXmlCanvas2D(root);
 * var imgExport = new mxImageExport();
 * imgExport.drawState(graph.getView().getState(graph.model.root), xmlCanvas);
 * 
 * var bounds = graph.getGraphBounds();
 * var w = Math.ceil(bounds.x + bounds.width);
 * var h = Math.ceil(bounds.y + bounds.height);
 * 
 * var xml = mxUtils.getXml(root);
 * new mxXmlRequest('export', 'format=png&w=' + w +
 * 		'&h=' + h + '&bg=#F9F7ED&xml=' + encodeURIComponent(xml))
 * 		.simulate(document, '_blank');
 * (end)
 * 
 * In order to export images for a graph whose container is not visible or not
 * part of the DOM, the following workaround can be used to compute the size of
 * the labels.
 * 
 * (code)
 * mxText.prototype.getTableSize = function(table)
 * {
 *   var oldParent = table.parentNode;
 *   
 *   document.body.appendChild(table);
 *   var size = new mxRectangle(0, 0, table.offsetWidth, table.offsetHeight);
 *   oldParent.appendChild(table);
 *   
 *   return size;
 * };
 * (end) 
 * 
 * Constructor: mxImageExport
 * 
 * Constructs a new image export.
 */
function mxImageExport()
{
	this.initShapes();
	this.initMarkers();
};

/**
 * Variable: includeOverlays
 * 
 * Specifies if overlays should be included in the export. Default is false.
 */
mxImageExport.prototype.includeOverlays = false;

/**
 * Variable: glassSize
 * 
 * Reference to the thread while the animation is running.
 */
mxImageExport.prototype.glassSize = 0.4;

/**
 * Variable: shapes
 * 
 * Holds implementations for the built-in shapes.
 */
mxImageExport.prototype.shapes = null;

/**
 * Variable: markers
 * 
 * Holds implementations for the built-in markers.
 */
mxImageExport.prototype.markers = null;

/**
 * Function: drawState
 * 
 * Draws the given state and all its descendants to the given canvas.
 */
mxImageExport.prototype.drawState = function(state, canvas)
{
	if (state != null)
	{
		if (state.shape != null)
		{
			var shape = (state.shape.stencil != null) ?
				state.shape.stencil :
				this.shapes[state.style[mxConstants.STYLE_SHAPE]];

			if (shape == null)
			{
				// Checks if there is a custom shape
				if (typeof(state.shape.redrawPath) == 'function')
				{
					shape = this.createShape(state, canvas);
				}
				// Uses a rectangle for all vertices where no shape can be found
				else if (state.view.graph.getModel().isVertex(state.cell))
				{
					shape = this.shapes['rectangle'];
				}
			}
			
			if (shape != null)
			{
				this.drawShape(state, canvas, shape);

				if (this.includeOverlays)
				{
					this.drawOverlays(state, canvas);
				}
			}
		}
		
		var graph = state.view.graph;
		var childCount = graph.model.getChildCount(state.cell);
		
		for (var i = 0; i < childCount; i++)
		{
			var childState = graph.view.getState(graph.model.getChildAt(state.cell, i));
			this.drawState(childState, canvas);
		}
	}
};

/**
 * Function: createShape
 * 
 * Creates a shape wrapper for the custom shape in the given cell state and
 * links its output to the given canvas.
 */
mxImageExport.prototype.createShape = function(state, canvas)
{
	return {
		drawShape: function(canvas, state, bounds, background)
		{
			var path =
			{
				translate: new mxPoint(bounds.x, bounds.y),
				moveTo: function(x, y)
				{
					canvas.moveTo(this.translate.x + x, this.translate.y + y);
				},
				lineTo: function(x, y)
				{
					canvas.lineTo(this.translate.x + x, this.translate.y + y);
				},
				quadTo: function(x1, y1, x, y)
				{
					canvas.quadTo(this.translate.x + x1, this.translate.y + y1, this.translate.x + x, this.translate.y + y);
				},
				curveTo: function(x1, y1, x2, y2, x, y)
				{
					canvas.curveTo(this.translate.x + x1, this.translate.y + y1, this.translate.x + x2, this.translate.y + y2, this.translate.x + x, this.translate.y + y);
				},
				end: function()
				{
					// do nothing
				},
				close: function()
				{
					canvas.close();
				}
			};
			
			if (!background)
			{
				canvas.fillAndStroke();
			}
			
			// LATER: Remove empty path if shape does not implement foreground, add shadow/clipping
			canvas.begin();
			state.shape.redrawPath.call(state.shape, path, bounds.x, bounds.y, bounds.width, bounds.height, !background);
			
			if (!background)
			{
				canvas.fillAndStroke();
			}
			
			return true;
		}
	};
};

/**
 * Function: drawOverlays
 * 
 * Draws the overlays for the given state. This is called if 
 * is true.
 */
mxImageExport.prototype.drawOverlays = function(state, canvas)
{
	if (state.overlays != null)
	{
		state.overlays.visit(function(id, shape)
		{
			var bounds = shape.bounds;
			
			if (bounds != null)
			{
				canvas.image(bounds.x, bounds.y, bounds.width, bounds.height, shape.image);
			}
		});
	}
};

/**
 * Function: drawShape
 * 
 * Draws the given state to the given canvas.
 */
mxImageExport.prototype.drawShape = function(state, canvas, shape)
{
	var rotation = mxUtils.getNumber(state.style, mxConstants.STYLE_ROTATION, 0);
	var direction = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION, null);

	// New styles for shape flipping the stencil
	var flipH = state.style[mxConstants.STYLE_STENCIL_FLIPH];
	var flipV = state.style[mxConstants.STYLE_STENCIL_FLIPV];
	
	if (flipH ? !flipV : flipV)
	{
		rotation *= -1;
	}
	
	// Default direction is east (ignored if rotation exists)
	if (direction != null)
	{
		if (direction == 'north')
		{
			rotation += 270;
		}
		else if (direction == 'west')
		{
			rotation += 180;
		}
		else if (direction == 'south')
		{
			rotation += 90;
		}
	}

	if (flipH && flipV)
	{
		rotation += 180;
		flipH = false;
		flipV = false;
	}

	// Saves the global state for each cell
	canvas.save();

	// Adds rotation and horizontal/vertical flipping
	// FIXME: Rotation and stencil flip only supported for stencil shapes
	rotation = rotation % 360;

	if (rotation != 0 || flipH || flipV)
	{
		canvas.rotate(rotation, flipH, flipV, state.getCenterX(), state.getCenterY());
	}

	// Note: Overwritten in mxStencil.paintShape (can depend on aspect)
	var scale = state.view.scale;
	var sw = mxUtils.getNumber(state.style, mxConstants.STYLE_STROKEWIDTH, 1) * scale;
	canvas.setStrokeWidth(sw);

	var sw2 = sw / 2;
	var bg = this.getBackgroundBounds(state);
	
	// Stencils will rotate the bounds as required
	if (state.shape.stencil == null && (direction == 'south' || direction == 'north'))
	{
		var dx = (bg.width - bg.height) / 2;
		bg.x += dx;
		bg.y += -dx;
		var tmp = bg.width;
		bg.width = bg.height;
		bg.height = tmp;
	}
	
	var bb = new mxRectangle(bg.x - sw2, bg.y - sw2, bg.width + sw, bg.height + sw);
	var alpha = mxUtils.getValue(state.style, mxConstants.STYLE_OPACITY, 100) / 100;

	var shp = state.style[mxConstants.STYLE_SHAPE];
	var imageShape = shp == mxConstants.SHAPE_IMAGE;
	var gradientColor = (imageShape) ? null : mxUtils.getValue(state.style, mxConstants.STYLE_GRADIENTCOLOR);
	
	// Converts colors with special keyword none to null
	if (gradientColor == mxConstants.NONE)
	{
		gradientColor = null;
	}

	var fcKey = (imageShape) ? mxConstants.STYLE_IMAGE_BACKGROUND : mxConstants.STYLE_FILLCOLOR; 
	var fillColor = mxUtils.getValue(state.style, fcKey, null);
	
	if (fillColor == mxConstants.NONE)
	{
		fillColor = null;
	}

	var scKey = (imageShape) ? mxConstants.STYLE_IMAGE_BORDER : mxConstants.STYLE_STROKECOLOR; 
	var strokeColor = mxUtils.getValue(state.style, scKey, null);
	
	if (strokeColor == mxConstants.NONE)
	{
		strokeColor = null;
	}

	var glass = (fillColor != null && (shp == mxConstants.SHAPE_LABEL || shp == mxConstants.SHAPE_RECTANGLE));
	
	// Draws the shadow if the fillColor is not transparent
	if (mxUtils.getValue(state.style, mxConstants.STYLE_SHADOW, false))
	{
		this.drawShadow(canvas, state, shape, rotation, flipH, flipV, bg, alpha, fillColor != null);
	}
	
	canvas.setAlpha(alpha);
	
	// Sets the dashed state
	if (mxUtils.getValue(state.style, mxConstants.STYLE_DASHED, '0') == '1')
	{
		canvas.setDashed(true);
		
		// Supports custom dash patterns
		var dash = state.style['dashPattern'];
		
		if (dash != null)
		{
			canvas.setDashPattern(dash);
		}
	}

	// Draws background and foreground
	if (strokeColor != null || fillColor != null)
	{
		if (strokeColor != null)
		{
			canvas.setStrokeColor(strokeColor);
		}
		
		if (fillColor != null)
		{
			if (gradientColor != null && gradientColor != 'transparent')
			{
				canvas.setGradient(fillColor, gradientColor, bg.x, bg.y, bg.width, bg.height, direction);
			}
			else 
			{
				canvas.setFillColor(fillColor);
			}
		}
		
		// Draws background and foreground of shape
		glass = shape.drawShape(canvas, state, bg, true, false) && glass;
		shape.drawShape(canvas, state, bg, false, false);
	}

	// Draws the glass effect
	// Requires background in generic shape for clipping
	if (glass && mxUtils.getValue(state.style, mxConstants.STYLE_GLASS, 0) == 1)
	{
		this.drawGlass(state, canvas, bb, shape, this.glassSize);
	}
	
	// Draws the image (currently disabled for everything but image and label shapes)
	if (imageShape || shp == mxConstants.SHAPE_LABEL)
	{
		var src = state.view.graph.getImage(state);
		
		if (src != null)
		{
			var imgBounds = this.getImageBounds(state);
			
			if (imgBounds != null)
			{
				this.drawImage(state, canvas, imgBounds, src);
			}
		}
	}

	// Restores canvas state
	canvas.restore();

	// Draws the label (label has separate rotation)
	var txt = state.text;
	
	// Does not use mxCellRenderer.getLabelValue to avoid conversion of HTML entities for VML
	var label = state.view.graph.getLabel(state.cell);
	
	if (txt != null && label != null && label.length > 0)
	{
		canvas.save();
		canvas.setAlpha(mxUtils.getValue(state.style, mxConstants.STYLE_TEXT_OPACITY, 100) / 100);
		var bounds = new mxRectangle(txt.boundingBox.x, txt.boundingBox.y, txt.boundingBox.width, txt.boundingBox.height);
		var vert = mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, 1) == 0;
		
		// Vertical error offset
		bounds.y += 2;

		if (vert)
		{
			if (txt.dialect != mxConstants.DIALECT_SVG)
			{
				var cx = bounds.x + bounds.width / 2;
				var cy = bounds.y + bounds.height / 2;
				var tmp = bounds.width;
				bounds.width = bounds.height;
				bounds.height = tmp;
				bounds.x = cx - bounds.width / 2;
				bounds.y = cy - bounds.height / 2;
			}
			else if (txt.dialect == mxConstants.DIALECT_SVG)
			{
				// Workarounds for different label bounding boxes (mostly ignoring rotation).
				// LATER: Fix in mxText so that the bounding box is consistent and rotated.
				// TODO: Check non-center/middle-aligned vertical labels in VML for IE8.
				var b = state.y + state.height;
				var cx = bounds.getCenterX() - state.x;
				var cy = bounds.getCenterY() - state.y;
				
				var y = b - cx - bounds.height / 2;
				bounds.x = state.x + cy - bounds.width / 2;
				bounds.y = y;
				//bounds.x -= state.height / 2 - state.width / 2;
				//bounds.y -= state.width / 2 - state.height / 2;
			}
		}
		
		this.drawLabelBackground(state, canvas, bounds, vert);
		this.drawLabel(state, canvas, bounds, vert, label);
		canvas.restore();
	}
};

/**
 * Function: drawGlass
 * 
 * Draws the given state to the given canvas.
 */
mxImageExport.prototype.drawShadow = function(canvas, state, shape, rotation, flipH, flipV, bounds, alpha, filled)
{
	// Requires background in generic shape for shadow, looks like only one
	// fillAndStroke is allowed per current path, try working around that
	// Computes rotated shadow offset
	var rad = rotation * Math.PI / 180;
	var cos = Math.cos(-rad);
	var sin = Math.sin(-rad);
	var offset = mxUtils.getRotatedPoint(new mxPoint(mxConstants.SHADOW_OFFSET_X, mxConstants.SHADOW_OFFSET_Y), cos, sin);
	
	if (flipH)
	{
		offset.x *= -1;
	}
	
	if (flipV)
	{
		offset.y *= -1;
	}
	
	// TODO: Use save/restore instead of negative offset to restore (requires fix for HTML canvas)
	canvas.translate(offset.x, offset.y);
	
	// Returns true if a shadow has been painted (path has been created)
	if (shape.drawShape(canvas, state, bounds, true, true))
	{
		canvas.setAlpha(mxConstants.SHADOW_OPACITY * alpha);
		canvas.shadow(mxConstants.SHADOWCOLOR, filled);
	}

	canvas.translate(-offset.x, -offset.y);
};

/**
 * Function: drawGlass
 * 
 * Draws the given state to the given canvas.
 */
mxImageExport.prototype.drawGlass = function(state, canvas, bounds, shape, size)
{
	// LATER: Clipping region should include stroke
	if (shape.drawShape(canvas, state, bounds, true, false))
	{
		canvas.save();
		canvas.clip();
		canvas.setGlassGradient(bounds.x, bounds.y, bounds.width, bounds.height);
	
		canvas.begin();
		canvas.moveTo(bounds.x, bounds.y);
		canvas.lineTo(bounds.x, (bounds.y + bounds.height * size));
		canvas.quadTo((bounds.x + bounds.width * 0.5),
				(bounds.y + bounds.height * 0.7), bounds.x + bounds.width,
				(bounds.y + bounds.height * size));
		canvas.lineTo(bounds.x + bounds.width, bounds.y);
		canvas.close();

		canvas.fill();
		canvas.restore();
	}
};

/**
 * Function: drawImage
 * 
 * Draws the given state to the given canvas.
 */
mxImageExport.prototype.drawImage = function(state, canvas, bounds, image)
{
	var aspect = mxUtils.getValue(state.style, mxConstants.STYLE_IMAGE_ASPECT, 1) == 1;
	var flipH = mxUtils.getValue(state.style, mxConstants.STYLE_IMAGE_FLIPH, 0) == 1;
	var flipV = mxUtils.getValue(state.style, mxConstants.STYLE_IMAGE_FLIPV, 0) == 1;
	
	canvas.image(bounds.x, bounds.y, bounds.width, bounds.height, image, aspect, flipH, flipV);
};

/**
 * Function: drawLabelBackground
 * 
 * Draws background for the label of the given state to the given canvas.
 */
mxImageExport.prototype.drawLabelBackground = function(state, canvas, bounds, vert)
{
	var stroke = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_BORDERCOLOR);
	var fill = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_BACKGROUNDCOLOR);
	
	if (stroke == mxConstants.NONE)
	{
		stroke = null;
	}
	
	if (fill == mxConstants.NONE)
	{
		fill = null;
	}
	
	if (stroke != null || fill != null)
	{
		var x = bounds.x;
		var y = bounds.y - mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_PADDING, 0);
		var w = bounds.width;
		var h = bounds.height;
		
		if (vert)
		{
			x += (w - h) / 2;
			y += (h - w) / 2;
			var tmp = w;
			w = h;
			h = tmp;
		}
		
		if (fill != null)
		{
			canvas.setFillColor(fill);
		}
		
		if (stroke != null)
		{
			canvas.setStrokeColor(stroke);
			canvas.setStrokeWidth(1);
			canvas.setDashed(false);
		}
		
		canvas.rect(x, y, w, h);

		if (fill != null && stroke != null)
		{
			canvas.fillAndStroke();
		}
		else if (fill != null)
		{
			canvas.fill();
		}
		else if (stroke != null)
		{
			canvas.stroke();
		}
	}
};

/**
 * Function: drawLabel
 * 
 * Draws the given state to the given canvas.
 */
mxImageExport.prototype.drawLabel = function(state, canvas, bounds, vert, str)
{
	var scale = state.view.scale;
	
	// Applies color
	canvas.setFontColor(mxUtils.getValue(state.style, mxConstants.STYLE_FONTCOLOR, '#000000'));
	
	// Applies font settings
	canvas.setFontFamily(mxUtils.getValue(state.style, mxConstants.STYLE_FONTFAMILY,
			mxConstants.DEFAULT_FONTFAMILY));
	canvas.setFontStyle(mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0));
	canvas.setFontSize(mxUtils.getValue(state.style, mxConstants.STYLE_FONTSIZE,
			mxConstants.DEFAULT_FONTSIZE) * scale);
	
	var align = mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT);
	
	// Uses null alignment for default values (valign default is 'top' which is fine)
	if (align == 'left')
	{
		align = null;
	}
		
	var y = bounds.y - mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_PADDING, 0);
	var wrap = state.view.graph.isWrapping(state.cell);
	var html = state.view.graph.isHtmlLabel(state.cell);
	
	// Replaces linefeeds in HTML markup to match the display output
	if (html && mxText.prototype.replaceLinefeeds)
	{
		str = str.replace(/\n/g, '
'); } canvas.text(bounds.x, y, bounds.width, bounds.height, str, align, null, vert, wrap, (html) ? 'html' : ''); }; /** * Function: getBackgroundBounds * * Draws the given state to the given canvas. */ mxImageExport.prototype.getBackgroundBounds = function(state) { if (state.style[mxConstants.STYLE_SHAPE] == mxConstants.SHAPE_SWIMLANE) { var scale = state.view.scale; var start = mxUtils.getValue(state.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE) * scale; var w = state.width; var h = state.height; if (mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, true)) { h = start; } else { w = start; } return new mxRectangle(state.x, state.y, Math.min(state.width, w), Math.min(state.height, h)); } else { return new mxRectangle(state.x, state.y, state.width, state.height); } }; /** * Function: getImageBounds * * Draws the given state to the given canvas. */ mxImageExport.prototype.getImageBounds = function(state) { var bounds = new mxRectangle(state.x, state.y, state.width, state.height); var style = state.style; if (mxUtils.getValue(style, mxConstants.STYLE_SHAPE) != mxConstants.SHAPE_IMAGE) { var imgAlign = mxUtils.getValue(style, mxConstants.STYLE_IMAGE_ALIGN, mxConstants.ALIGN_LEFT); var imgValign = mxUtils.getValue(style, mxConstants.STYLE_IMAGE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE); var imgWidth = mxUtils.getValue(style, mxConstants.STYLE_IMAGE_WIDTH, mxConstants.DEFAULT_IMAGESIZE); var imgHeight = mxUtils.getValue(style, mxConstants.STYLE_IMAGE_HEIGHT, mxConstants.DEFAULT_IMAGESIZE); var spacing = mxUtils.getValue(style, mxConstants.STYLE_SPACING, 2); if (imgAlign == mxConstants.ALIGN_CENTER) { bounds.x += (bounds.width - imgWidth) / 2; } else if (imgAlign == mxConstants.ALIGN_RIGHT) { bounds.x += bounds.width - imgWidth - spacing - 2; } else // LEFT { bounds.x += spacing + 4; } if (imgValign == mxConstants.ALIGN_TOP) { bounds.y += spacing; } else if (imgValign == mxConstants.ALIGN_BOTTOM) { bounds.y += bounds.height - imgHeight - spacing; } else // MIDDLE { bounds.y += (bounds.height - imgHeight) / 2; } bounds.width = imgWidth; bounds.height = imgHeight; } return bounds; }; /** * Function: drawMarker * * Initializes the built-in shapes. */ mxImageExport.prototype.drawMarker = function(canvas, state, source) { var offset = null; // Computes the norm and the inverse norm var pts = state.absolutePoints; var n = pts.length; var p0 = (source) ? pts[1] : pts[n - 2]; var pe = (source) ? pts[0] : pts[n - 1]; var dx = pe.x - p0.x; var dy = pe.y - p0.y; var dist = Math.max(1, Math.sqrt(dx * dx + dy * dy)); var unitX = dx / dist; var unitY = dy / dist; var size = mxUtils.getValue(state.style, (source) ? mxConstants.STYLE_STARTSIZE : mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE); // Allow for stroke width in the end point used and the // orthogonal vectors describing the direction of the marker // TODO: Should get strokewidth from canvas (same for strokecolor) var sw = mxUtils.getValue(state.style, mxConstants.STYLE_STROKEWIDTH, 1); pe = pe.clone(); var type = mxUtils.getValue(state.style, (source) ? mxConstants.STYLE_STARTARROW : mxConstants.STYLE_ENDARROW); var f = this.markers[type]; if (f != null) { offset = f(canvas, state, type, pe, unitX, unitY, size, source, sw); } return offset; }; /** * Function: initShapes * * Initializes the built-in shapes. */ mxImageExport.prototype.initShapes = function() { this.shapes = []; // Implements the rectangle and rounded rectangle shape this.shapes['rectangle'] = { drawShape: function(canvas, state, bounds, background) { if (background) { // Paints the shape if (mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false)) { var f = mxUtils.getValue(state.style, mxConstants.STYLE_ARCSIZE, mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100; var r = Math.min(bounds.width * f, bounds.height * f); canvas.roundrect(bounds.x, bounds.y, bounds.width, bounds.height, r, r); } else { canvas.rect(bounds.x, bounds.y, bounds.width, bounds.height); } return true; } else { canvas.fillAndStroke(); } } }; // Implements the swimlane shape this.shapes['swimlane'] = { drawShape: function(canvas, state, bounds, background) { if (background) { if (mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false)) { var r = Math.min(bounds.width * mxConstants.RECTANGLE_ROUNDING_FACTOR, bounds.height * mxConstants.RECTANGLE_ROUNDING_FACTOR); canvas.roundrect(bounds.x, bounds.y, bounds.width, bounds.height, r, r); } else { canvas.rect(bounds.x, bounds.y, bounds.width, bounds.height); } return true; } else { canvas.fillAndStroke(); canvas.begin(); var x = state.x; var y = state.y; var w = state.width; var h = state.height; if (mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, 1) == 0) { x += bounds.width; w -= bounds.width; canvas.moveTo(x, y); canvas.lineTo(x + w, y); canvas.lineTo(x + w, y + h); canvas.lineTo(x, y + h); } else { y += bounds.height; h -= bounds.height; canvas.moveTo(x, y); canvas.lineTo(x, y + h); canvas.lineTo(x + w, y + h); canvas.lineTo(x + w, y); } canvas.stroke(); } } }; this.shapes['image'] = this.shapes['rectangle']; this.shapes['label'] = this.shapes['rectangle']; var imageExport = this; this.shapes['connector'] = { translatePoint: function(points, index, offset) { if (offset != null) { var pt = points[index].clone(); pt.x += offset.x; pt.y += offset.y; points[index] = pt; } }, drawShape: function(canvas, state, bounds, background, shadow) { if (background) { // Does not draw the markers in the shadow to match the display canvas.setFillColor((shadow) ? mxConstants.NONE : mxUtils.getValue(state.style, mxConstants.STYLE_STROKECOLOR, "#000000")); canvas.setDashed(false); var pts = state.absolutePoints.slice(); this.translatePoint(pts, 0, imageExport.drawMarker(canvas, state, true)); this.translatePoint(pts, pts.length - 1, imageExport.drawMarker(canvas, state, false)); canvas.setDashed(mxUtils.getValue(state.style, mxConstants.STYLE_DASHED, '0') == '1'); canvas.begin(); var pt = pts[0]; canvas.moveTo(pt.x, pt.y); if (mxUtils.getValue(state.style, mxConstants.STYLE_CURVED, false)) { var n = pts.length; for (var i = 1; i < n - 2; i++) { var p0 = pts[i]; var p1 = pts[i + 1]; var ix = (p0.x + p1.x) / 2; var iy = (p0.y + p1.y) / 2; canvas.quadTo(p0.x, p0.y, ix, iy); } var p0 = pts[n - 2]; var p1 = pts[n - 1]; canvas.quadTo(p0.x, p0.y, p1.x, p1.y); } else { var rounded = mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false); var arcSize = mxConstants.LINE_ARCSIZE / 2; var pe = pts[pts.length - 1]; canvas.moveTo(pt.x, pt.y); // Draws the line segments for (var i = 1; i < pts.length - 1; i++) { var tmp = pts[i]; var dx = pt.x - tmp.x; var dy = pt.y - tmp.y; if ((rounded && i < pts.length - 1) && (dx != 0 || dy != 0)) { // Draws a line from the last point to the current // point with a spacing of size off the current point // into direction of the last point var dist = Math.sqrt(dx * dx + dy * dy); var nx1 = dx * Math.min(arcSize, dist / 2) / dist; var ny1 = dy * Math.min(arcSize, dist / 2) / dist; var x1 = tmp.x + nx1; var y1 = tmp.y + ny1; canvas.lineTo(x1, y1); // Draws a curve from the last point to the current // point with a spacing of size off the current point // into direction of the next point var next = pts[i + 1]; dx = next.x - tmp.x; dy = next.y - tmp.y; dist = Math.max(1, Math.sqrt(dx * dx + dy * dy)); var nx2 = dx * Math.min(arcSize, dist / 2) / dist; var ny2 = dy * Math.min(arcSize, dist / 2) / dist; var x2 = tmp.x + nx2; var y2 = tmp.y + ny2; canvas.curveTo(tmp.x, tmp.y, tmp.x, tmp.y, x2, y2); tmp = new mxPoint(x2, y2); } else { canvas.lineTo(tmp.x, tmp.y); } pt = tmp; } canvas.lineTo(pe.x, pe.y); } canvas.stroke(); return true; } else { // no foreground } } }; this.shapes['arrow'] = { drawShape: function(canvas, state, bounds, background) { if (background) { // Geometry of arrow var spacing = mxConstants.ARROW_SPACING; var width = mxConstants.ARROW_WIDTH; var arrow = mxConstants.ARROW_SIZE; // Base vector (between end points) var pts = state.absolutePoints; var p0 = pts[0]; var pe = pts[pts.length - 1]; var dx = pe.x - p0.x; var dy = pe.y - p0.y; var dist = Math.sqrt(dx * dx + dy * dy); var length = dist - 2 * spacing - arrow; // Computes the norm and the inverse norm var nx = dx / dist; var ny = dy / dist; var basex = length * nx; var basey = length * ny; var floorx = width * ny/3; var floory = -width * nx/3; // Computes points var p0x = p0.x - floorx / 2 + spacing * nx; var p0y = p0.y - floory / 2 + spacing * ny; var p1x = p0x + floorx; var p1y = p0y + floory; var p2x = p1x + basex; var p2y = p1y + basey; var p3x = p2x + floorx; var p3y = p2y + floory; // p4 not necessary var p5x = p3x - 3 * floorx; var p5y = p3y - 3 * floory; canvas.begin(); canvas.moveTo(p0x, p0y); canvas.lineTo(p1x, p1y); canvas.lineTo(p2x, p2y); canvas.lineTo(p3x, p3y); canvas.lineTo(pe.x - spacing * nx, pe.y - spacing * ny); canvas.lineTo(p5x, p5y); canvas.lineTo(p5x + floorx, p5y + floory); canvas.close(); return true; } else { canvas.fillAndStroke(); } } }; this.shapes['cylinder'] = { drawShape: function(canvas, state, bounds, background) { if (background) { return false; } else { var x = bounds.x; var y = bounds.y; var w = bounds.width; var h = bounds.height; var dy = Math.min(mxCylinder.prototype.maxHeight, Math.floor(h / 5)); canvas.begin(); canvas.moveTo(x, y + dy); canvas.curveTo(x, y - dy / 3, x + w, y - dy / 3, x + w, y + dy); canvas.lineTo(x + w, y + h - dy); canvas.curveTo(x + w, y + h + dy / 3, x, y + h + dy / 3, x, y + h - dy); canvas.close(); canvas.fillAndStroke(); canvas.begin(); canvas.moveTo(x, y + dy); canvas.curveTo(x, y + 2 * dy, x + w, y + 2 * dy, x + w, y + dy); canvas.stroke(); } } }; this.shapes['line'] = { drawShape: function(canvas, state, bounds, background) { if (background) { return false; } else { canvas.begin(); var mid = state.getCenterY(); canvas.moveTo(bounds.x, mid); canvas.lineTo(bounds.x + bounds.width, mid); canvas.stroke(); } } }; this.shapes['ellipse'] = { drawShape: function(canvas, state, bounds, background) { if (background) { canvas.ellipse(bounds.x, bounds.y, bounds.width, bounds.height); return true; } else { canvas.fillAndStroke(); } } }; this.shapes['doubleEllipse'] = { drawShape: function(canvas, state, bounds, background) { var x = bounds.x; var y = bounds.y; var w = bounds.width; var h = bounds.height; if (background) { canvas.ellipse(x, y, w, h); return true; } else { canvas.fillAndStroke(); var inset = Math.min(4, Math.min(w / 5, h / 5)); x += inset; y += inset; w -= 2 * inset; h -= 2 * inset; if (w > 0 && h > 0) { canvas.ellipse(x, y, w, h); } canvas.stroke(); } } }; this.shapes['triangle'] = { drawShape: function(canvas, state, bounds, background) { if (background) { var x = bounds.x; var y = bounds.y; var w = bounds.width; var h = bounds.height; canvas.begin(); canvas.moveTo(x, y); canvas.lineTo(x + w, y + h / 2); canvas.lineTo(x, y + h); canvas.close(); return true; } else { canvas.fillAndStroke(); } } }; this.shapes['rhombus'] = { drawShape: function(canvas, state, bounds, background) { if (background) { var x = bounds.x; var y = bounds.y; var w = bounds.width; var h = bounds.height; var hw = w / 2; var hh = h / 2; canvas.begin(); canvas.moveTo(x + hw, y); canvas.lineTo(x + w, y + hh); canvas.lineTo(x + hw, y + h); canvas.lineTo(x, y + hh); canvas.close(); return true; } else { canvas.fillAndStroke(); } } }; this.shapes['hexagon'] = { drawShape: function(canvas, state, bounds, background) { if (background) { var x = bounds.x; var y = bounds.y; var w = bounds.width; var h = bounds.height; canvas.begin(); canvas.moveTo(x + 0.25 * w, y); canvas.lineTo(x + 0.75 * w, y); canvas.lineTo(x + w, y + 0.5 * h); canvas.lineTo(x + 0.75 * w, y + h); canvas.lineTo(x + 0.25 * w, y + h); canvas.lineTo(x, y + 0.5 * h); canvas.close(); return true; } else { canvas.fillAndStroke(); } } }; this.shapes['actor'] = { drawShape: function(canvas, state, bounds, background) { if (background) { var x = bounds.x; var y = bounds.y; var w = bounds.width; var h = bounds.height; var width = w * 2 / 6; canvas.begin(); canvas.moveTo(x, y + h); canvas.curveTo(x, y + 3 * h / 5, x, y + 2 * h / 5, x + w / 2, y + 2 * h / 5); canvas.curveTo(x + w / 2 - width, y + 2 * h / 5, x + w / 2 - width, y, x + w / 2, y); canvas.curveTo(x + w / 2 + width, y, x + w / 2 + width, y + 2 * h / 5, x + w / 2, y + 2 * h / 5); canvas.curveTo(x + w, y + 2 * h / 5, x + w, y + 3 * h / 5, x + w, y + h); canvas.close(); return true; } else { canvas.fillAndStroke(); } } }; this.shapes['cloud'] = { drawShape: function(canvas, state, bounds, background) { if (background) { var x = bounds.x; var y = bounds.y; var w = bounds.width; var h = bounds.height; canvas.begin(); canvas.moveTo(x + 0.25 * w, y + 0.25 * h); canvas.curveTo(x + 0.05 * w, y + 0.25 * h, x, y + 0.5 * h, x + 0.16 * w, y + 0.55 * h); canvas.curveTo(x, y + 0.66 * h, x + 0.18 * w, y + 0.9 * h, x + 0.31 * w, y + 0.8 * h); canvas.curveTo(x + 0.4 * w, y + h, x + 0.7 * w, y + h, x + 0.8 * w, y + 0.8 * h); canvas.curveTo(x + w, y + 0.8 * h, x + w, y + 0.6 * h, x + 0.875 * w, y + 0.5 * h); canvas.curveTo(x + w, y + 0.3 * h, x + 0.8 * w, y + 0.1 * h, x + 0.625 * w, y + 0.2 * h); canvas.curveTo(x + 0.5 * w, y + 0.05 * h, x + 0.3 * w, y + 0.05 * h, x + 0.25 * w, y + 0.25 * h); canvas.close(); return true; } else { canvas.fillAndStroke(); } } }; }; /** * Function: initMarkers * * Initializes the built-in markers. */ mxImageExport.prototype.initMarkers = function() { this.markers = []; var tmp = function(canvas, state, type, pe, unitX, unitY, size, source, sw) { // The angle of the forward facing arrow sides against the x axis is // 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for // only half the strokewidth is processed ). var endOffsetX = unitX * sw * 1.118; var endOffsetY = unitY * sw * 1.118; pe.x -= endOffsetX; pe.y -= endOffsetY; unitX = unitX * (size + sw); unitY = unitY * (size + sw); canvas.begin(); canvas.moveTo(pe.x, pe.y); canvas.lineTo(pe.x - unitX - unitY / 2, pe.y - unitY + unitX / 2); if (type == mxConstants.ARROW_CLASSIC) { canvas.lineTo(pe.x - unitX * 3 / 4, pe.y - unitY * 3 / 4); } canvas.lineTo(pe.x + unitY / 2 - unitX, pe.y - unitY - unitX / 2); canvas.close(); var key = (source) ? mxConstants.STYLE_STARTFILL : mxConstants.STYLE_ENDFILL; if (state.style[key] == 0) { canvas.stroke(); } else { canvas.fillAndStroke(); } var f = (type != mxConstants.ARROW_CLASSIC) ? 1 : 3 / 4; return new mxPoint(-unitX * f - endOffsetX, -unitY * f - endOffsetY); }; this.markers['classic'] = tmp; this.markers['block'] = tmp; this.markers['open'] = function(canvas, state, type, pe, unitX, unitY, size, source, sw) { // The angle of the forward facing arrow sides against the x axis is // 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for // only half the strokewidth is processed ). var endOffsetX = unitX * sw * 1.118; var endOffsetY = unitY * sw * 1.118; pe.x -= endOffsetX; pe.y -= endOffsetY; unitX = unitX * (size + sw); unitY = unitY * (size + sw); canvas.begin(); canvas.moveTo(pe.x - unitX - unitY / 2, pe.y - unitY + unitX / 2); canvas.lineTo(pe.x, pe.y); canvas.lineTo(pe.x + unitY / 2 - unitX, pe.y - unitY - unitX / 2); canvas.stroke(); return new mxPoint(-endOffsetX * 2, -endOffsetY * 2); }; this.markers['oval'] = function(canvas, state, type, pe, unitX, unitY, size, source, sw) { var a = size / 2; canvas.ellipse(pe.x - a, pe.y - a, size, size); var key = (source) ? mxConstants.STYLE_STARTFILL : mxConstants.STYLE_ENDFILL; if (state.style[key] == 0) { canvas.stroke(); } else { canvas.fillAndStroke(); } return new mxPoint(-unitX / 2, -unitY / 2); }; var tmp_diamond = function(canvas, state, type, pe, unitX, unitY, size, source, sw) { // The angle of the forward facing arrow sides against the x axis is // 45 degrees, 1/sin(45) = 1.4142 / 2 = 0.7071 ( / 2 allows for // only half the strokewidth is processed ). Or 0.9862 for thin diamond. // Note these values and the tk variable below are dependent, update // both together (saves trig hard coding it). var swFactor = (type == mxConstants.ARROW_DIAMOND) ? 0.7071 : 0.9862; var endOffsetX = unitX * sw * swFactor; var endOffsetY = unitY * sw * swFactor; unitX = unitX * (size + sw); unitY = unitY * (size + sw); pe.x -= endOffsetX; pe.y -= endOffsetY; // thickness factor for diamond var tk = ((type == mxConstants.ARROW_DIAMOND) ? 2 : 3.4); canvas.begin(); canvas.moveTo(pe.x, pe.y); canvas.lineTo(pe.x - unitX / 2 - unitY / tk, pe.y + unitX / tk - unitY / 2); canvas.lineTo(pe.x - unitX, pe.y - unitY); canvas.lineTo(pe.x - unitX / 2 + unitY / tk, pe.y - unitY / 2 - unitX / tk); canvas.close(); var key = (source) ? mxConstants.STYLE_STARTFILL : mxConstants.STYLE_ENDFILL; if (state.style[key] == 0) { canvas.stroke(); } else { canvas.fillAndStroke(); } return new mxPoint(-endOffsetX - unitX, -endOffsetY - unitY); }; this.markers['diamond'] = tmp_diamond; this.markers['diamondThin'] = tmp_diamond; }; /** * $Id: mxXmlCanvas2D.js,v 1.9 2012-04-24 13:56:56 gaudenz Exp $ * Copyright (c) 2006-2010, JGraph Ltd */ /** * * Class: mxXmlCanvas2D * * Implements a canvas to be used with . This canvas writes all * calls as child nodes to the given root XML node. * * (code) * var xmlDoc = mxUtils.createXmlDocument(); * var root = xmlDoc.createElement('output'); * xmlDoc.appendChild(root); * var xmlCanvas = new mxXmlCanvas2D(root); * (end) * * Constructor: mxXmlCanvas2D * * Constructs a XML canvas. * * Parameters: * * root - XML node for adding child nodes. */ var mxXmlCanvas2D = function(root) { /** * Variable: converter * * Holds the to convert image URLs. */ var converter = new mxUrlConverter(); /** * Variable: compressed * * Specifies if the output should be compressed by removing redundant calls. * Default is true. */ var compressed = true; /** * Variable: textEnabled * * Specifies if text output should be enabled. Default is true. */ var textEnabled = true; // Private reference to the owner document var doc = root.ownerDocument; // Implements stack for save/restore var stack = []; // Implements state for redundancy checks var state = { alpha: 1, dashed: false, strokewidth: 1, fontsize: mxConstants.DEFAULT_FONTSIZE, fontfamily: mxConstants.DEFAULT_FONTFAMILY, fontcolor: '#000000' }; // Private helper function set set precision to 2 var f2 = function(x) { return Math.round(parseFloat(x) * 100) / 100; }; // Returns public interface return { /** * Function: getConverter * * Returns . */ getConverter: function() { return converter; }, /** * Function: isCompressed * * Returns . */ isCompressed: function() { return compressed; }, /** * Function: setCompressed * * Sets . */ setCompressed: function(value) { compressed = value; }, /** * Function: isTextEnabled * * Returns . */ isTextEnabled: function() { return textEnabled; }, /** * Function: setTextEnabled * * Sets . */ setTextEnabled: function(value) { textEnabled = value; }, /** * Function: getDocument * * Returns the owner document of the root element. */ getDocument: function() { return doc; }, /** * Function: save * * Saves the state of the graphics object. */ save: function() { if (compressed) { stack.push(state); state = mxUtils.clone(state); } root.appendChild(doc.createElement('save')); }, /** * Function: restore * * Restores the state of the graphics object. */ restore: function() { if (compressed) { state = stack.pop(); } root.appendChild(doc.createElement('restore')); }, /** * Function: scale * * Scales the current graphics object. */ scale: function(value) { var elem = doc.createElement('scale'); elem.setAttribute('scale', value); root.appendChild(elem); }, /** * Function: translate * * Translates the current graphics object. */ translate: function(dx, dy) { var elem = doc.createElement('translate'); elem.setAttribute('dx', f2(dx)); elem.setAttribute('dy', f2(dy)); root.appendChild(elem); }, /** * Function: rotate * * Rotates and/or flips the current graphics object. */ rotate: function(theta, flipH, flipV, cx, cy) { var elem = doc.createElement('rotate'); elem.setAttribute('theta', f2(theta)); elem.setAttribute('flipH', (flipH) ? '1' : '0'); elem.setAttribute('flipV', (flipV) ? '1' : '0'); elem.setAttribute('cx', f2(cx)); elem.setAttribute('cy', f2(cy)); root.appendChild(elem); }, /** * Function: setStrokeWidth * * Sets the stroke width. */ setStrokeWidth: function(value) { if (compressed) { if (state.strokewidth == value) { return; } state.strokewidth = value; } var elem = doc.createElement('strokewidth'); elem.setAttribute('width', f2(value)); root.appendChild(elem); }, /** * Function: setStrokeColor * * Sets the stroke color. */ setStrokeColor: function(value) { var elem = doc.createElement('strokecolor'); elem.setAttribute('color', value); root.appendChild(elem); }, /** * Function: setDashed * * Sets the dashed state to true or false. */ setDashed: function(value) { if (compressed) { if (state.dashed == value) { return; } state.dashed = value; } var elem = doc.createElement('dashed'); elem.setAttribute('dashed', (value) ? '1' : '0'); root.appendChild(elem); }, /** * Function: setDashPattern * * Sets the dashed pattern to the given space separated list of numbers. */ setDashPattern: function(value) { var elem = doc.createElement('dashpattern'); elem.setAttribute('pattern', value); root.appendChild(elem); }, /** * Function: setLineCap * * Sets the linecap. */ setLineCap: function(value) { var elem = doc.createElement('linecap'); elem.setAttribute('cap', value); root.appendChild(elem); }, /** * Function: setLineJoin * * Sets the linejoin. */ setLineJoin: function(value) { var elem = doc.createElement('linejoin'); elem.setAttribute('join', value); root.appendChild(elem); }, /** * Function: setMiterLimit * * Sets the miterlimit. */ setMiterLimit: function(value) { var elem = doc.createElement('miterlimit'); elem.setAttribute('limit', value); root.appendChild(elem); }, /** * Function: setFontSize * * Sets the fontsize. */ setFontSize: function(value) { if (textEnabled) { if (compressed) { if (state.fontsize == value) { return; } state.fontsize = value; } var elem = doc.createElement('fontsize'); elem.setAttribute('size', value); root.appendChild(elem); } }, /** * Function: setFontColor * * Sets the fontcolor. */ setFontColor: function(value) { if (textEnabled) { if (compressed) { if (state.fontcolor == value) { return; } state.fontcolor = value; } var elem = doc.createElement('fontcolor'); elem.setAttribute('color', value); root.appendChild(elem); } }, /** * Function: setFontFamily * * Sets the fontfamily. */ setFontFamily: function(value) { if (textEnabled) { if (compressed) { if (state.fontfamily == value) { return; } state.fontfamily = value; } var elem = doc.createElement('fontfamily'); elem.setAttribute('family', value); root.appendChild(elem); } }, /** * Function: setFontStyle * * Sets the fontstyle. */ setFontStyle: function(value) { if (textEnabled) { var elem = doc.createElement('fontstyle'); elem.setAttribute('style', value); root.appendChild(elem); } }, /** * Function: setAlpha * * Sets the current alpha. */ setAlpha: function(alpha) { if (compressed) { if (state.alpha == alpha) { return; } state.alpha = alpha; } var elem = doc.createElement('alpha'); elem.setAttribute('alpha', f2(alpha)); root.appendChild(elem); }, /** * Function: setFillColor * * Sets the fillcolor. */ setFillColor: function(value) { var elem = doc.createElement('fillcolor'); elem.setAttribute('color', value); root.appendChild(elem); }, /** * Function: setGradient * * Sets the gradient color. */ setGradient: function(color1, color2, x, y, w, h, direction) { var elem = doc.createElement('gradient'); elem.setAttribute('c1', color1); elem.setAttribute('c2', color2); elem.setAttribute('x', f2(x)); elem.setAttribute('y', f2(y)); elem.setAttribute('w', f2(w)); elem.setAttribute('h', f2(h)); // Default direction is south if (direction != null) { elem.setAttribute('direction', direction); } root.appendChild(elem); }, /** * Function: setGlassGradient * * Sets the glass gradient. */ setGlassGradient: function(x, y, w, h) { var elem = doc.createElement('glass'); elem.setAttribute('x', f2(x)); elem.setAttribute('y', f2(y)); elem.setAttribute('w', f2(w)); elem.setAttribute('h', f2(h)); root.appendChild(elem); }, /** * Function: rect * * Sets the current path to a rectangle. */ rect: function(x, y, w, h) { var elem = doc.createElement('rect'); elem.setAttribute('x', f2(x)); elem.setAttribute('y', f2(y)); elem.setAttribute('w', f2(w)); elem.setAttribute('h', f2(h)); root.appendChild(elem); }, /** * Function: roundrect * * Sets the current path to a rounded rectangle. */ roundrect: function(x, y, w, h, dx, dy) { var elem = doc.createElement('roundrect'); elem.setAttribute('x', f2(x)); elem.setAttribute('y', f2(y)); elem.setAttribute('w', f2(w)); elem.setAttribute('h', f2(h)); elem.setAttribute('dx', f2(dx)); elem.setAttribute('dy', f2(dy)); root.appendChild(elem); }, /** * Function: ellipse * * Sets the current path to an ellipse. */ ellipse: function(x, y, w, h) { var elem = doc.createElement('ellipse'); elem.setAttribute('x', f2(x)); elem.setAttribute('y', f2(y)); elem.setAttribute('w', f2(w)); elem.setAttribute('h', f2(h)); root.appendChild(elem); }, /** * Function: image * * Paints an image. */ image: function(x, y, w, h, src, aspect, flipH, flipV) { src = converter.convert(src); // TODO: Add option for embedding images as base64 var elem = doc.createElement('image'); elem.setAttribute('x', f2(x)); elem.setAttribute('y', f2(y)); elem.setAttribute('w', f2(w)); elem.setAttribute('h', f2(h)); elem.setAttribute('src', src); elem.setAttribute('aspect', (aspect) ? '1' : '0'); elem.setAttribute('flipH', (flipH) ? '1' : '0'); elem.setAttribute('flipV', (flipV) ? '1' : '0'); root.appendChild(elem); }, /** * Function: text * * Paints the given text. */ text: function(x, y, w, h, str, align, valign, vertical, wrap, format) { if (textEnabled) { var elem = doc.createElement('text'); elem.setAttribute('x', f2(x)); elem.setAttribute('y', f2(y)); elem.setAttribute('w', f2(w)); elem.setAttribute('h', f2(h)); elem.setAttribute('str', str); if (align != null) { elem.setAttribute('align', align); } if (valign != null) { elem.setAttribute('valign', valign); } elem.setAttribute('vertical', (vertical) ? '1' : '0'); elem.setAttribute('wrap', (wrap) ? '1' : '0'); elem.setAttribute('format', format); root.appendChild(elem); } }, /** * Function: begin * * Starts a new path. */ begin: function() { root.appendChild(doc.createElement('begin')); }, /** * Function: moveTo * * Moves the current path the given coordinates. */ moveTo: function(x, y) { var elem = doc.createElement('move'); elem.setAttribute('x', f2(x)); elem.setAttribute('y', f2(y)); root.appendChild(elem); }, /** * Function: lineTo * * Adds a line to the current path. */ lineTo: function(x, y) { var elem = doc.createElement('line'); elem.setAttribute('x', f2(x)); elem.setAttribute('y', f2(y)); root.appendChild(elem); }, /** * Function: quadTo * * Adds a quadratic curve to the current path. */ quadTo: function(x1, y1, x2, y2) { var elem = doc.createElement('quad'); elem.setAttribute('x1', f2(x1)); elem.setAttribute('y1', f2(y1)); elem.setAttribute('x2', f2(x2)); elem.setAttribute('y2', f2(y2)); root.appendChild(elem); }, /** * Function: curveTo * * Adds a bezier curve to the current path. */ curveTo: function(x1, y1, x2, y2, x3, y3) { var elem = doc.createElement('curve'); elem.setAttribute('x1', f2(x1)); elem.setAttribute('y1', f2(y1)); elem.setAttribute('x2', f2(x2)); elem.setAttribute('y2', f2(y2)); elem.setAttribute('x3', f2(x3)); elem.setAttribute('y3', f2(y3)); root.appendChild(elem); }, /** * Function: close * * Closes the current path. */ close: function() { root.appendChild(doc.createElement('close')); }, /** * Function: stroke * * Paints the outline of the current path. */ stroke: function() { root.appendChild(doc.createElement('stroke')); }, /** * Function: fill * * Fills the current path. */ fill: function() { root.appendChild(doc.createElement('fill')); }, /** * Function: fillstroke * * Fills and paints the outline of the current path. */ fillAndStroke: function() { root.appendChild(doc.createElement('fillstroke')); }, /** * Function: shadow * * Paints the current path as a shadow of the given color. */ shadow: function(value, filled) { var elem = doc.createElement('shadow'); elem.setAttribute('value', value); if (filled != null) { elem.setAttribute('filled', (filled) ? '1' : '0'); } root.appendChild(elem); }, /** * Function: clip * * Uses the current path for clipping. */ clip: function() { root.appendChild(doc.createElement('clip')); } }; };/** * $Id: mxSvgCanvas2D.js,v 1.18 2012-11-23 15:13:19 gaudenz Exp $ * Copyright (c) 2006-2010, JGraph Ltd */ /** * * Class: mxSvgCanvas2D * * Implements a canvas to be used with . This canvas writes all * calls as SVG output to the given SVG root node. * * (code) * var svgDoc = mxUtils.createXmlDocument(); * var root = (svgDoc.createElementNS != null) ? * svgDoc.createElementNS(mxConstants.NS_SVG, 'svg') : svgDoc.createElement('svg'); * * if (svgDoc.createElementNS == null) * { * root.setAttribute('xmlns', mxConstants.NS_SVG); * } * * var bounds = graph.getGraphBounds(); * root.setAttribute('width', (bounds.x + bounds.width + 4) + 'px'); * root.setAttribute('height', (bounds.y + bounds.height + 4) + 'px'); * root.setAttribute('version', '1.1'); * * svgDoc.appendChild(root); * * var svgCanvas = new mxSvgCanvas2D(root); * (end) * * Constructor: mxSvgCanvas2D * * Constructs an SVG canvas. * * Parameters: * * root - SVG container for the output. * styleEnabled - Optional boolean that specifies if a style section should be * added. The style section sets the default font-size, font-family and * stroke-miterlimit globally. Default is false. */ var mxSvgCanvas2D = function(root, styleEnabled) { styleEnabled = (styleEnabled != null) ? styleEnabled : false; /** * Variable: converter * * Holds the to convert image URLs. */ var converter = new mxUrlConverter(); /** * Variable: autoAntiAlias * * Specifies if anti aliasing should be disabled for rectangles * and orthogonal paths. Default is true. */ var autoAntiAlias = true; /** * Variable: textEnabled * * Specifies if text output should be enabled. Default is true. */ var textEnabled = true; /** * Variable: foEnabled * * Specifies if use of foreignObject for HTML markup is allowed. Default is true. */ var foEnabled = true; // Private helper function to create SVG elements var create = function(tagName, namespace) { var doc = root.ownerDocument || document; if (doc.createElementNS != null) { return doc.createElementNS(namespace || mxConstants.NS_SVG, tagName); } else { var elt = doc.createElement(tagName); if (namespace != null) { elt.setAttribute('xmlns', namespace); } return elt; } }; // Defs section contains optional style and gradients var defs = create('defs'); // Creates defs section with optional global style if (styleEnabled) { var style = create('style'); style.setAttribute('type', 'text/css'); mxUtils.write(style, 'svg{font-family:' + mxConstants.DEFAULT_FONTFAMILY + ';font-size:' + mxConstants.DEFAULT_FONTSIZE + ';fill:none;stroke-miterlimit:10}'); if (autoAntiAlias) { mxUtils.write(style, 'rect{shape-rendering:crispEdges}'); } // Appends style to defs and defs to SVG container defs.appendChild(style); } root.appendChild(defs); // Defines the current state var currentState = { dx: 0, dy: 0, scale: 1, transform: '', fill: null, gradient: null, stroke: null, strokeWidth: 1, dashed: false, dashpattern: '3 3', alpha: 1, linecap: 'flat', linejoin: 'miter', miterlimit: 10, fontColor: '#000000', fontSize: mxConstants.DEFAULT_FONTSIZE, fontFamily: mxConstants.DEFAULT_FONTFAMILY, fontStyle: 0 }; // Local variables var currentPathIsOrthogonal = true; var glassGradient = null; var currentNode = null; var currentPath = null; var lastPoint = null; var gradients = []; var refCount = 0; var stack = []; // Other private helper methods var createGradientId = function(start, end, direction) { // Removes illegal characters from gradient ID if (start.charAt(0) == '#') { start = start.substring(1); } if (end.charAt(0) == '#') { end = end.substring(1); } // Workaround for gradient IDs not working in Safari 5 / Chrome 6 // if they contain uppercase characters start = start.toLowerCase(); end = end.toLowerCase(); // Wrong gradient directions possible? var dir = null; if (direction == null || direction == mxConstants.DIRECTION_SOUTH) { dir = 's'; } else if (direction == mxConstants.DIRECTION_EAST) { dir = 'e'; } else { var tmp = start; start = end; end = tmp; if (direction == mxConstants.DIRECTION_NORTH) { dir = 's'; } else if (direction == mxConstants.DIRECTION_WEST) { dir = 'e'; } } return start+'-'+end+'-'+dir; }; var createHtmlBody = function(str, align, valign) { var style = 'margin:0px;font-size:' + Math.floor(currentState.fontSize) + 'px;' + 'font-family:' + currentState.fontFamily + ';color:' + currentState.fontColor+ ';'; if ((currentState.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) { style += 'font-weight:bold;'; } if ((currentState.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) { style += 'font-style:italic;'; } if ((currentState.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) { style += 'font-decoration:underline;'; } if (align == mxConstants.ALIGN_CENTER) { style += 'text-align:center;'; } else if (align == mxConstants.ALIGN_RIGHT) { style += 'text-align:right;'; } // Converts HTML entities to unicode var t = document.createElement('div'); t.innerHTML = str; str = t.innerHTML.replace(/ /g, ' '); // LATER: Add vertical align support via table, adds xmlns to workaround empty NS in IE9 standards var node = mxUtils.parseXml('
' + str + '
').documentElement; return node; }; var getSvgGradient = function(start, end, direction) { var id = createGradientId(start, end, direction); var gradient = gradients[id]; if (gradient == null) { gradient = create('linearGradient'); gradient.setAttribute('id', ++refCount); gradient.setAttribute('x1', '0%'); gradient.setAttribute('y1', '0%'); gradient.setAttribute('x2', '0%'); gradient.setAttribute('y2', '0%'); if (direction == null || direction == mxConstants.DIRECTION_SOUTH) { gradient.setAttribute('y2', '100%'); } else if (direction == mxConstants.DIRECTION_EAST) { gradient.setAttribute('x2', '100%'); } else if (direction == mxConstants.DIRECTION_NORTH) { gradient.setAttribute('y1', '100%'); } else if (direction == mxConstants.DIRECTION_WEST) { gradient.setAttribute('x1', '100%'); } var stop = create('stop'); stop.setAttribute('offset', '0%'); stop.setAttribute('style', 'stop-color:'+start); gradient.appendChild(stop); stop = create('stop'); stop.setAttribute('offset', '100%'); stop.setAttribute('style', 'stop-color:'+end); gradient.appendChild(stop); defs.appendChild(gradient); gradients[id] = gradient; } return gradient.getAttribute('id'); }; var appendNode = function(node, state, filled, stroked) { if (node != null) { if (state.clip != null) { node.setAttribute('clip-path', 'url(#' + state.clip + ')'); state.clip = null; } if (currentPath != null) { node.setAttribute('d', currentPath.join(' ')); currentPath = null; if (autoAntiAlias && currentPathIsOrthogonal) { node.setAttribute('shape-rendering', 'crispEdges'); state.strokeWidth = Math.max(1, state.strokeWidth); } } if (state.alpha < 1) { // LATER: Check if using fill/stroke-opacity here is faster node.setAttribute('opacity', state.alpha); //node.setAttribute('fill-opacity', state.alpha); //node.setAttribute('stroke-opacity', state.alpha); } if (filled && (state.fill != null || state.gradient != null)) { if (state.gradient != null) { node.setAttribute('fill', 'url(#' + state.gradient + ')'); } else { node.setAttribute('fill', state.fill.toLowerCase()); } } else if (!styleEnabled) { node.setAttribute('fill', 'none'); } if (stroked && state.stroke != null) { node.setAttribute('stroke', state.stroke.toLowerCase()); // Sets the stroke properties (1 is default is SVG) if (state.strokeWidth != 1) { if (node.nodeName == 'rect' && autoAntiAlias) { state.strokeWidth = Math.max(1, state.strokeWidth); } node.setAttribute('stroke-width', state.strokeWidth); } if (node.nodeName == 'path') { // Linejoin miter is default in SVG if (state.linejoin != null && state.linejoin != 'miter') { node.setAttribute('stroke-linejoin', state.linejoin); } if (state.linecap != null) { // flat is called butt in SVG var value = state.linecap; if (value == 'flat') { value = 'butt'; } // Linecap butt is default in SVG if (value != 'butt') { node.setAttribute('stroke-linecap', value); } } // Miterlimit 10 is default in our document if (state.miterlimit != null && (!styleEnabled || state.miterlimit != 10)) { node.setAttribute('stroke-miterlimit', state.miterlimit); } } if (state.dashed) { var dash = state.dashpattern.split(' '); if (dash.length > 0) { var pat = []; for (var i = 0; i < dash.length; i++) { pat[i] = Number(dash[i]) * currentState.strokeWidth; } node.setAttribute('stroke-dasharray', pat.join(' ')); } } } if (state.transform.length > 0) { node.setAttribute('transform', state.transform); } root.appendChild(node); } }; // Private helper function to format a number var f2 = function(x) { return Math.round(parseFloat(x) * 100) / 100; }; // Returns public interface return { /** * Function: getConverter * * Returns . */ getConverter: function() { return converter; }, /** * Function: isAutoAntiAlias * * Returns . */ isAutoAntiAlias: function() { return autoAntiAlias; }, /** * Function: setAutoAntiAlias * * Sets . */ setAutoAntiAlias: function(value) { autoAntiAlias = value; }, /** * Function: isTextEnabled * * Returns . */ isTextEnabled: function() { return textEnabled; }, /** * Function: setTextEnabled * * Sets . */ setTextEnabled: function(value) { textEnabled = value; }, /** * Function: isFoEnabled * * Returns . */ isFoEnabled: function() { return foEnabled; }, /** * Function: setFoEnabled * * Sets . */ setFoEnabled: function(value) { foEnabled = value; }, /** * Function: save * * Saves the state of the graphics object. */ save: function() { stack.push(currentState); currentState = mxUtils.clone(currentState); }, /** * Function: restore * * Restores the state of the graphics object. */ restore: function() { currentState = stack.pop(); }, /** * Function: scale * * Scales the current graphics object. */ scale: function(value) { currentState.scale *= value; currentState.strokeWidth *= value; }, /** * Function: translate * * Translates the current graphics object. */ translate: function(dx, dy) { currentState.dx += dx; currentState.dy += dy; }, /** * Function: rotate * * Rotates and/or flips the current graphics object. */ rotate: function(theta, flipH, flipV, cx, cy) { cx += currentState.dx; cy += currentState.dy; cx *= currentState.scale; cy *= currentState.scale; // This implementation uses custom scale/translate and built-in rotation // Rotation state is part of the AffineTransform in state.transform if (flipH ^ flipV) { var tx = (flipH) ? cx : 0; var sx = (flipH) ? -1 : 1; var ty = (flipV) ? cy : 0; var sy = (flipV) ? -1 : 1; currentState.transform += 'translate(' + f2(tx) + ',' + f2(ty) + ')'; currentState.transform += 'scale(' + f2(sx) + ',' + f2(sy) + ')'; currentState.transform += 'translate(' + f2(-tx) + ' ' + f2(-ty) + ')'; } currentState.transform += 'rotate(' + f2(theta) + ',' + f2(cx) + ',' + f2(cy) + ')'; }, /** * Function: setStrokeWidth * * Sets the stroke width. */ setStrokeWidth: function(value) { currentState.strokeWidth = value * currentState.scale; }, /** * Function: setStrokeColor * * Sets the stroke color. */ setStrokeColor: function(value) { currentState.stroke = value; }, /** * Function: setDashed * * Sets the dashed state to true or false. */ setDashed: function(value) { currentState.dashed = value; }, /** * Function: setDashPattern * * Sets the dashed pattern to the given space separated list of numbers. */ setDashPattern: function(value) { currentState.dashpattern = value; }, /** * Function: setLineCap * * Sets the linecap. */ setLineCap: function(value) { currentState.linecap = value; }, /** * Function: setLineJoin * * Sets the linejoin. */ setLineJoin: function(value) { currentState.linejoin = value; }, /** * Function: setMiterLimit * * Sets the miterlimit. */ setMiterLimit: function(value) { currentState.miterlimit = value; }, /** * Function: setFontSize * * Sets the fontsize. */ setFontSize: function(value) { currentState.fontSize = value; }, /** * Function: setFontColor * * Sets the fontcolor. */ setFontColor: function(value) { currentState.fontColor = value; }, /** * Function: setFontFamily * * Sets the fontfamily. */ setFontFamily: function(value) { currentState.fontFamily = value; }, /** * Function: setFontStyle * * Sets the fontstyle. */ setFontStyle: function(value) { currentState.fontStyle = value; }, /** * Function: setAlpha * * Sets the current alpha. */ setAlpha: function(alpha) { currentState.alpha = alpha; }, /** * Function: setFillColor * * Sets the fillcolor. */ setFillColor: function(value) { currentState.fill = value; currentState.gradient = null; }, /** * Function: setGradient * * Sets the gradient color. */ setGradient: function(color1, color2, x, y, w, h, direction) { if (color1 != null && color2 != null) { currentState.gradient = getSvgGradient(color1, color2, direction); currentState.fill = color1; } }, /** * Function: setGlassGradient * * Sets the glass gradient. */ setGlassGradient: function(x, y, w, h) { // Creates glass overlay gradient if (glassGradient == null) { glassGradient = create('linearGradient'); glassGradient.setAttribute('id', '0'); glassGradient.setAttribute('x1', '0%'); glassGradient.setAttribute('y1', '0%'); glassGradient.setAttribute('x2', '0%'); glassGradient.setAttribute('y2', '100%'); var stop1 = create('stop'); stop1.setAttribute('offset', '0%'); stop1.setAttribute('style', 'stop-color:#ffffff;stop-opacity:0.9'); glassGradient.appendChild(stop1); var stop2 = create('stop'); stop2.setAttribute('offset', '100%'); stop2.setAttribute('style', 'stop-color:#ffffff;stop-opacity:0.1'); glassGradient.appendChild(stop2); // Makes it the first entry of all gradients in defs if (defs.firstChild.nextSibling != null) { defs.insertBefore(glassGradient, defs.firstChild.nextSibling); } else { defs.appendChild(glassGradient); } } // Glass gradient has hardcoded ID (see above) currentState.gradient = '0'; }, /** * Function: rect * * Sets the current path to a rectangle. */ rect: function(x, y, w, h) { x += currentState.dx; y += currentState.dy; currentNode = create('rect'); currentNode.setAttribute('x', f2(x * currentState.scale)); currentNode.setAttribute('y', f2(y * currentState.scale)); currentNode.setAttribute('width', f2(w * currentState.scale)); currentNode.setAttribute('height', f2(h * currentState.scale)); if (!styleEnabled && autoAntiAlias) { currentNode.setAttribute('shape-rendering', 'crispEdges'); } }, /** * Function: roundrect * * Sets the current path to a rounded rectangle. */ roundrect: function(x, y, w, h, dx, dy) { x += currentState.dx; y += currentState.dy; currentNode = create('rect'); currentNode.setAttribute('x', f2(x * currentState.scale)); currentNode.setAttribute('y', f2(y * currentState.scale)); currentNode.setAttribute('width', f2(w * currentState.scale)); currentNode.setAttribute('height', f2(h * currentState.scale)); if (dx > 0) { currentNode.setAttribute('rx', f2(dx * currentState.scale)); } if (dy > 0) { currentNode.setAttribute('ry', f2(dy * currentState.scale)); } if (!styleEnabled && autoAntiAlias) { currentNode.setAttribute('shape-rendering', 'crispEdges'); } }, /** * Function: ellipse * * Sets the current path to an ellipse. */ ellipse: function(x, y, w, h) { x += currentState.dx; y += currentState.dy; currentNode = create('ellipse'); currentNode.setAttribute('cx', f2((x + w / 2) * currentState.scale)); currentNode.setAttribute('cy', f2((y + h / 2) * currentState.scale)); currentNode.setAttribute('rx', f2(w / 2 * currentState.scale)); currentNode.setAttribute('ry', f2(h / 2 * currentState.scale)); }, /** * Function: image * * Paints an image. */ image: function(x, y, w, h, src, aspect, flipH, flipV) { src = converter.convert(src); // TODO: Add option for embedded images as base64. Current // known issues are binary loading of cross-domain images. aspect = (aspect != null) ? aspect : true; flipH = (flipH != null) ? flipH : false; flipV = (flipV != null) ? flipV : false; x += currentState.dx; y += currentState.dy; var node = create('image'); node.setAttribute('x', f2(x * currentState.scale)); node.setAttribute('y', f2(y * currentState.scale)); node.setAttribute('width', f2(w * currentState.scale)); node.setAttribute('height', f2(h * currentState.scale)); if (mxClient.IS_VML) { node.setAttribute('xlink:href', src); } else { node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', src); } if (!aspect) { node.setAttribute('preserveAspectRatio', 'none'); } if (currentState.alpha < 1) { node.setAttribute('opacity', currentState.alpha); } var tr = currentState.transform; if (flipH || flipV) { var sx = 1; var sy = 1; var dx = 0; var dy = 0; if (flipH) { sx = -1; dx = -w - 2 * x; } if (flipV) { sy = -1; dy = -h - 2 * y; } // Adds image tansformation to existing transforms tr += 'scale(' + sx + ',' + sy + ')translate(' + dx + ',' + dy + ')'; } if (tr.length > 0) { node.setAttribute('transform', tr); } root.appendChild(node); }, /** * Function: text * * Paints the given text. Possible values for format are empty string for * plain text and html for HTML markup. */ text: function(x, y, w, h, str, align, valign, vertical, wrap, format) { if (textEnabled) { x += currentState.dx; y += currentState.dy; if (foEnabled && format == 'html') { var node = create('g'); node.setAttribute('transform', currentState.transform + 'scale(' + currentState.scale + ',' + currentState.scale + ')'); if (currentState.alpha < 1) { node.setAttribute('opacity', currentState.alpha); } var fo = create('foreignObject'); fo.setAttribute('x', Math.round(x)); fo.setAttribute('y', Math.round(y)); fo.setAttribute('width', Math.round(w)); fo.setAttribute('height', Math.round(h)); fo.appendChild(createHtmlBody(str, align, valign)); node.appendChild(fo); root.appendChild(node); } else { var size = Math.floor(currentState.fontSize); var node = create('g'); var tr = currentState.transform; if (vertical) { var cx = x + w / 2; var cy = y + h / 2; tr += 'rotate(-90,' + f2(cx * currentState.scale) + ',' + f2(cy * currentState.scale) + ')'; } if (tr.length > 0) { node.setAttribute('transform', tr); } if (currentState.alpha < 1) { node.setAttribute('opacity', currentState.alpha); } // Default is left var anchor = (align == mxConstants.ALIGN_RIGHT) ? 'end' : (align == mxConstants.ALIGN_CENTER) ? 'middle' : 'start'; if (anchor == 'end') { x += Math.max(0, w - 2); } else if (anchor == 'middle') { x += w / 2; } else { x += (w > 0) ? 2 : 0; } if ((currentState.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) { node.setAttribute('font-weight', 'bold'); } if ((currentState.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) { node.setAttribute('font-style', 'italic'); } if ((currentState.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) { node.setAttribute('text-decoration', 'underline'); } // Text-anchor start is default in SVG if (anchor != 'start') { node.setAttribute('text-anchor', anchor); } if (!styleEnabled || size != mxConstants.DEFAULT_FONTSIZE) { node.setAttribute('font-size', Math.floor(size * currentState.scale) + 'px'); } if (!styleEnabled || currentState.fontFamily != mxConstants.DEFAULT_FONTFAMILY) { node.setAttribute('font-family', currentState.fontFamily); } node.setAttribute('fill', currentState.fontColor); var lines = str.split('\n'); var lineHeight = size * 1.25; var textHeight = (h > 0) ? size + (lines.length - 1) * lineHeight : lines.length * lineHeight - 1; var dy = h - textHeight; // Top is default if (valign == null || valign == mxConstants.ALIGN_TOP) { y = Math.max(y - 3 * currentState.scale, y + dy / 2 + ((h > 0) ? lineHeight / 2 - 8 : 0)); } else if (valign == mxConstants.ALIGN_MIDDLE) { y = y + dy / 2; } else if (valign == mxConstants.ALIGN_BOTTOM) { y = Math.min(y, y + dy + 2 * currentState.scale); } y += size; for (var i = 0; i < lines.length; i++) { var text = create('text'); text.setAttribute('x', f2(x * currentState.scale)); text.setAttribute('y', f2(y * currentState.scale)); mxUtils.write(text, lines[i]); node.appendChild(text); y += size * 1.3; } root.appendChild(node); } } }, /** * Function: begin * * Starts a new path. */ begin: function() { currentNode = create('path'); currentPath = []; lastPoint = null; currentPathIsOrthogonal = true; }, /** * Function: moveTo * * Moves the current path the given coordinates. */ moveTo: function(x, y) { if (currentPath != null) { x += currentState.dx; y += currentState.dy; currentPath.push('M ' + f2(x * currentState.scale) + ' ' + f2(y * currentState.scale)); if (autoAntiAlias) { lastPoint = new mxPoint(x, y); } } }, /** * Function: lineTo * * Adds a line to the current path. */ lineTo: function(x, y) { if (currentPath != null) { x += currentState.dx; y += currentState.dy; currentPath.push('L ' + f2(x * currentState.scale) + ' ' + f2(y * currentState.scale)); if (autoAntiAlias) { if (lastPoint != null && currentPathIsOrthogonal && x != lastPoint.x && y != lastPoint.y) { currentPathIsOrthogonal = false; } lastPoint = new mxPoint(x, y); } } }, /** * Function: quadTo * * Adds a quadratic curve to the current path. */ quadTo: function(x1, y1, x2, y2) { if (currentPath != null) { x1 += currentState.dx; y1 += currentState.dy; x2 += currentState.dx; y2 += currentState.dy; currentPath.push('Q ' + f2(x1 * currentState.scale) + ' ' + f2(y1 * currentState.scale) + ' ' + f2(x2 * currentState.scale) + ' ' + f2(y2 * currentState.scale)); currentPathIsOrthogonal = false; } }, /** * Function: curveTo * * Adds a bezier curve to the current path. */ curveTo: function(x1, y1, x2, y2, x3, y3) { if (currentPath != null) { x1 += currentState.dx; y1 += currentState.dy; x2 += currentState.dx; y2 += currentState.dy; x3 += currentState.dx; y3 += currentState.dy; currentPath.push('C ' + f2(x1 * currentState.scale) + ' ' + f2(y1 * currentState.scale) + ' ' + f2(x2 * currentState.scale) + ' ' + f2(y2 * currentState.scale) +' ' + f2(x3 * currentState.scale) + ' ' + f2(y3 * currentState.scale)); currentPathIsOrthogonal = false; } }, /** * Function: close * * Closes the current path. */ close: function() { if (currentPath != null) { currentPath.push('Z'); } }, /** * Function: stroke * * Paints the outline of the current path. */ stroke: function() { appendNode(currentNode, currentState, false, true); }, /** * Function: fill * * Fills the current path. */ fill: function() { appendNode(currentNode, currentState, true, false); }, /** * Function: fillstroke * * Fills and paints the outline of the current path. */ fillAndStroke: function() { appendNode(currentNode, currentState, true, true); }, /** * Function: shadow * * Paints the current path as a shadow of the given color. */ shadow: function(value, filled) { this.save(); this.setStrokeColor(value); if (filled) { this.setFillColor(value); this.fillAndStroke(); } else { this.stroke(); } this.restore(); }, /** * Function: clip * * Uses the current path for clipping. */ clip: function() { if (currentNode != null) { if (currentPath != null) { currentNode.setAttribute('d', currentPath.join(' ')); currentPath = null; } var id = ++refCount; var clip = create('clipPath'); clip.setAttribute('id', id); clip.appendChild(currentNode); defs.appendChild(clip); currentState.clip = id; } } }; };/** * $Id: mxGuide.js,v 1.7 2012-04-13 12:53:30 gaudenz Exp $ * Copyright (c) 2006-2010, JGraph Ltd */ /** * Class: mxGuide * * Implements the alignment of selection cells to other cells in the graph. * * Constructor: mxGuide * * Constructs a new guide object. */ function mxGuide(graph, states) { this.graph = graph; this.setStates(states); }; /** * Variable: graph * * Reference to the enclosing instance. */ mxGuide.prototype.graph = null; /** * Variable: states * * Contains the that are used for alignment. */ mxGuide.prototype.states = null; /** * Variable: horizontal * * Specifies if horizontal guides are enabled. Default is true. */ mxGuide.prototype.horizontal = true; /** * Variable: vertical * * Specifies if vertical guides are enabled. Default is true. */ mxGuide.prototype.vertical = true; /** * Variable: vertical * * Holds the for the horizontal guide. */ mxGuide.prototype.guideX = null; /** * Variable: vertical * * Holds the for the vertical guide. */ mxGuide.prototype.guideY = null; /** * Variable: crisp * * Specifies if theguide should be rendered in crisp mode if applicable. * Default is true. */ mxGuide.prototype.crisp = true; /** * Function: setStates * * Sets the that should be used for alignment. */ mxGuide.prototype.setStates = function(states) { this.states = states; }; /** * Function: isEnabledForEvent * * Returns true if the guide should be enabled for the given native event. This * implementation always returns true. */ mxGuide.prototype.isEnabledForEvent = function(evt) { return true; }; /** * Function: getGuideTolerance * * Returns the tolerance for the guides. Default value is * gridSize * scale / 2. */ mxGuide.prototype.getGuideTolerance = function() { return this.graph.gridSize * this.graph.view.scale / 2; }; /** * Function: createGuideShape * * Returns the mxShape to be used for painting the respective guide. This * implementation returns a new, dashed and crisp using * and as the format. * * Parameters: * * horizontal - Boolean that specifies which guide should be created. */ mxGuide.prototype.createGuideShape = function(horizontal) { var guide = new mxPolyline([], mxConstants.GUIDE_COLOR, mxConstants.GUIDE_STROKEWIDTH); guide.crisp = this.crisp; guide.isDashed = true; return guide; }; /** * Function: move * * Moves the by the given and returnt the snapped point. */ mxGuide.prototype.move = function(bounds, delta, gridEnabled) { if (this.states != null && (this.horizontal || this.vertical) && bounds != null && delta != null) { var trx = this.graph.getView().translate; var scale = this.graph.getView().scale; var dx = delta.x; var dy = delta.y; var overrideX = false; var overrideY = false; var tt = this.getGuideTolerance(); var ttX = tt; var ttY = tt; var b = bounds.clone(); b.x += delta.x; b.y += delta.y; var left = b.x; var right = b.x + b.width; var center = b.getCenterX(); var top = b.y; var bottom = b.y + b.height; var middle = b.getCenterY(); // Snaps the left, center and right to the given x-coordinate function snapX(x) { x += this.graph.panDx; var override = false; if (Math.abs(x - center) < ttX) { dx = x - bounds.getCenterX(); ttX = Math.abs(x - center); override = true; } else if (Math.abs(x - left) < ttX) { dx = x - bounds.x; ttX = Math.abs(x - left); override = true; } else if (Math.abs(x - right) < ttX) { dx = x - bounds.x - bounds.width; ttX = Math.abs(x - right); override = true; } if (override) { if (this.guideX == null) { this.guideX = this.createGuideShape(true); // Makes sure to use either VML or SVG shapes in order to implement // event-transparency on the background area of the rectangle since // HTML shapes do not let mouseevents through even when transparent this.guideX.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; this.guideX.init(this.graph.getView().getOverlayPane()); if (this.graph.dialect == mxConstants.DIALECT_SVG) { this.guideX.node.setAttribute('pointer-events', 'none'); this.guideX.pipe.setAttribute('pointer-events', 'none'); } } var c = this.graph.container; x -= this.graph.panDx; this.guideX.points = [new mxPoint(x, -this.graph.panDy), new mxPoint(x, c.scrollHeight - 3 - this.graph.panDy)]; } overrideX = overrideX || override; }; // Snaps the top, middle or bottom to the given y-coordinate function snapY(y) { y += this.graph.panDy; var override = false; if (Math.abs(y - middle) < ttY) { dy = y - bounds.getCenterY(); ttY = Math.abs(y - middle); override = true; } else if (Math.abs(y - top) < ttY) { dy = y - bounds.y; ttY = Math.abs(y - top); override = true; } else if (Math.abs(y - bottom) < ttY) { dy = y - bounds.y - bounds.height; ttY = Math.abs(y - bottom); override = true; } if (override) { if (this.guideY == null) { this.guideY = this.createGuideShape(false); // Makes sure to use either VML or SVG shapes in order to implement // event-transparency on the background area of the rectangle since // HTML shapes do not let mouseevents through even when transparent this.guideY.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; this.guideY.init(this.graph.getView().getOverlayPane()); if (this.graph.dialect == mxConstants.DIALECT_SVG) { this.guideY.node.setAttribute('pointer-events', 'none'); this.guideY.pipe.setAttribute('pointer-events', 'none'); } } var c = this.graph.container; y -= this.graph.panDy; this.guideY.points = [new mxPoint(-this.graph.panDx, y), new mxPoint(c.scrollWidth - 3 - this.graph.panDx, y)]; } overrideY = overrideY || override; }; for (var i = 0; i < this.states.length; i++) { var state = this.states[i]; if (state != null) { // Align x if (this.horizontal) { snapX.call(this, state.getCenterX()); snapX.call(this, state.x); snapX.call(this, state.x + state.width); } // Align y if (this.vertical) { snapY.call(this, state.getCenterY()); snapY.call(this, state.y); snapY.call(this, state.y + state.height); } } } if (!overrideX && this.guideX != null) { this.guideX.node.style.visibility = 'hidden'; } else if (this.guideX != null) { this.guideX.node.style.visibility = 'visible'; this.guideX.redraw(); } if (!overrideY && this.guideY != null) { this.guideY.node.style.visibility = 'hidden'; } else if (this.guideY != null) { this.guideY.node.style.visibility = 'visible'; this.guideY.redraw(); } // Moves cells that are off-grid back to the grid on move if (gridEnabled) { if (!overrideX) { var tx = bounds.x - (this.graph.snap(bounds.x / scale - trx.x) + trx.x) * scale; dx = this.graph.snap(dx / scale) * scale - tx; } if (!overrideY) { var ty = bounds.y - (this.graph.snap(bounds.y / scale - trx.y) + trx.y) * scale; dy = this.graph.snap(dy / scale) * scale - ty; } } delta = new mxPoint(dx, dy); } return delta; }; /** * Function: hide * * Hides all current guides. */ mxGuide.prototype.hide = function() { if (this.guideX != null) { this.guideX.node.style.visibility = 'hidden'; } if (this.guideY != null) { this.guideY.node.style.visibility = 'hidden'; } }; /** * Function: destroy * * Destroys all resources that this object uses. */ mxGuide.prototype.destroy = function() { if (this.guideX != null) { this.guideX.destroy(); this.guideX = null; } if (this.guideY != null) { this.guideY.destroy(); this.guideY = null; } }; /** * $Id: mxShape.js,v 1.175 2013-01-16 08:40:17 gaudenz Exp $ * Copyright (c) 2006-2010, JGraph Ltd */ /** * Class: mxShape * * Base class for all shapes. A shape in mxGraph is a * separate implementation for SVG, VML and HTML. Which * implementation to use is controlled by the * property which is assigned from within the * when the shape is created. The dialect must be assigned * for a shape, and it does normally depend on the browser and * the confiuration of the graph (see rendering hint). * * For each supported shape in SVG and VML, a corresponding * shape exists in mxGraph, namely for text, image, rectangle, * rhombus, ellipse and polyline. The other shapes are a * combination of these shapes (eg. label and swimlane) * or they consist of one or more (filled) path objects * (eg. actor and cylinder). The HTML implementation is * optional but may be required for a HTML-only view of * the graph. * * Custom Shapes: * * To extend from this class, the basic code looks as follows. * In the special case where the custom shape consists only of * one filled region or one filled region and an additional stroke * the and should be subclassed, * respectively. These implement in order to create * the path expression for VML and SVG via a unified API (see * ). has an additional boolean * argument to draw the foreground and background separately. * * (code) * function CustomShape() { } * * CustomShape.prototype = new mxShape(); * CustomShape.prototype.constructor = CustomShape; * (end) * * To register a custom shape in an existing graph instance, * one must register the shape under a new name in the graph's * cell renderer as follows: * * (code) * graph.cellRenderer.registerShape('customShape', CustomShape); * (end) * * The second argument is the name of the constructor. * * In order to use the shape you can refer to the given name above * in a stylesheet. For example, to change the shape for the default * vertex style, the following code is used: * * (code) * var style = graph.getStylesheet().getDefaultVertexStyle(); * style[mxConstants.STYLE_SHAPE] = 'customShape'; * (end) * * Constructor: mxShape * * Constructs a new shape. */ function mxShape() { }; /** * Variable: SVG_STROKE_TOLERANCE * * Event-tolerance for SVG strokes (in px). Default is 8. */ mxShape.prototype.SVG_STROKE_TOLERANCE = 8; /** * Variable: scale * * Holds the scale in which the shape is being painted. */ mxShape.prototype.scale = 1; /** * Variable: dialect * * Holds the dialect in which the shape is to be painted. * This can be one of the DIALECT constants in . */ mxShape.prototype.dialect = null; /** * Variable: crisp * * Special attribute for SVG rendering to set the shape-rendering attribute to * crispEdges in the output. This is ignored in IE. Default is false. To * disable antialias in IE, the explorer.css file can be changed as follows: * * [code] * v\:* { * behavior: url(#default#VML); * antialias: false; * } * [/code] */ mxShape.prototype.crisp = false; /** * Variable: roundedCrispSvg * * Specifies if crisp rendering should be enabled for rounded shapes. * Default is true. */ mxShape.prototype.roundedCrispSvg = true; /** * Variable: mixedModeHtml * * Specifies if should be used in mixed Html mode. * Default is true. */ mxShape.prototype.mixedModeHtml = true; /** * Variable: preferModeHtml * * Specifies if should be used in prefer Html mode. * Default is true. */ mxShape.prototype.preferModeHtml = true; /** * Variable: bounds * * Holds the that specifies the bounds of this shape. */ mxShape.prototype.bounds = null; /** * Variable: points * * Holds the array of that specify the points of this shape. */ mxShape.prototype.points = null; /** * Variable: node * * Holds the outermost DOM node that represents this shape. */ mxShape.prototype.node = null; /** * Variable: label * * Reference to the DOM node that should contain the label. This is null * if the label should be placed inside or . */ mxShape.prototype.label = null; /** * Variable: innerNode * * Holds the DOM node that graphically represents this shape. This may be * null if the outermost DOM represents this shape. */ mxShape.prototype.innerNode = null; /** * Variable: style * * Holds the style of the cell state that corresponds to this shape. This may * be null if the shape is used directly, without a cell state. */ mxShape.prototype.style = null; /** * Variable: startOffset * * Specifies the offset in pixels from the first point in and * the actual start of the shape. */ mxShape.prototype.startOffset = null; /** * Variable: endOffset * * Specifies the offset in pixels from the last point in and * the actual start of the shape. */ mxShape.prototype.endOffset = null; /** * Variable: boundingBox * * Contains the bounding box of the shape, that is, the smallest rectangle * that includes all pixels of the shape. */ mxShape.prototype.boundingBox = null; /** * Variable: vmlNodes * * Array if VML node names to fix in IE8 standards mode. */ mxShape.prototype.vmlNodes = ['node', 'strokeNode', 'fillNode', 'shadowNode']; /** * Variable: vmlScale * * Internal scaling for VML using coordsize for better precision. */ mxShape.prototype.vmlScale = 1; /** * Variable: strokewidth * * Holds the current strokewidth. Default is 1. */ mxShape.prototype.strokewidth = 1; /** * Function: setCursor * * Sets the cursor on the given shape. * * Parameters: * * cursor - The cursor to be used. */ mxShape.prototype.setCursor = function(cursor) { if (cursor == null) { cursor = ''; } this.cursor = cursor; if (this.innerNode != null) { this.innerNode.style.cursor = cursor; } if (this.node != null) { this.node.style.cursor = cursor; } if (this.pipe != null) { this.pipe.style.cursor = cursor; } }; /** * Function: getCursor * * Returns the current cursor. */ mxShape.prototype.getCursor = function() { return this.cursor; }; /** * Function: init * * Initializes the shape by creaing the DOM node using * and adding it into the given container. * * Parameters: * * container - DOM node that will contain the shape. */ mxShape.prototype.init = function(container) { if (this.node == null) { this.node = this.create(container); if (container != null) { container.appendChild(this.node); // Workaround for broken VML in IE8 standards mode. This gives an ID to // each element that is referenced from this instance. After adding the // DOM to the document, the outerHTML is overwritten to fix the VML // rendering and the references are restored. if (document.documentMode == 8 && mxUtils.isVml(this.node)) { this.reparseVml(); } } } // Gradients are inserted late when the owner SVG element is known if (this.insertGradientNode != null) { this.insertGradient(this.insertGradientNode); this.insertGradientNode = null; } }; /** * Function: reparseVml * * Forces a parsing of the outerHTML of this node and restores all references specified in . * This is a workaround for the VML rendering bug in IE8 standards mode. */ mxShape.prototype.reparseVml = function() { // Assigns temporary IDs to VML nodes so that references can be restored when // inserted into the DOM as a string for (var i = 0; i < this.vmlNodes.length; i++) { if (this[this.vmlNodes[i]] != null) { this[this.vmlNodes[i]].setAttribute('id', 'mxTemporaryReference-' + this.vmlNodes[i]); } } this.node.outerHTML = this.node.outerHTML; // Restores references to the actual DOM nodes for (var i = 0; i < this.vmlNodes.length; i++) { if (this[this.vmlNodes[i]] != null) { this[this.vmlNodes[i]] = this.node.ownerDocument.getElementById('mxTemporaryReference-' + this.vmlNodes[i]); this[this.vmlNodes[i]].removeAttribute('id'); } } }; /** * Function: insertGradient * * Inserts the given gradient node. */ mxShape.prototype.insertGradient = function(node) { // Gradients are inserted late when the owner SVG element is known if (node != null) { // Checks if the given gradient already exists inside the SVG element // that also contains the node that represents this shape. If the gradient // with the same ID exists in another SVG element, then this will add // a copy of the gradient with a different ID to the SVG element and update // the reference accordingly. This is required in Firefox because if the // referenced fill element is removed from the DOM the shape appears black. var count = 0; var id = node.getAttribute('id'); var gradient = document.getElementById(id); while (gradient != null && gradient.ownerSVGElement != this.node.ownerSVGElement) { count++; id = node.getAttribute('id') + '-' + count; gradient = document.getElementById(id); } // According to specification, gradients should be put in a defs // section in the first child of the owner SVG element. However, // it turns out that gradients only work when added as follows. if (gradient == null) { node.setAttribute('id', id); this.node.ownerSVGElement.appendChild(node); gradient = node; } if (gradient != null) { var ref = 'url(#' + id + ')'; var tmp = (this.innerNode != null) ? this.innerNode : this.node; if (tmp != null && tmp.getAttribute('fill') != ref) { tmp.setAttribute('fill', ref); } } } }; /** * Function: isMixedModeHtml * * Used to determine if a shape can be rendered using in mixed * mode Html without compromising the display accuracy. The default * implementation will check if the shape is not rounded or rotated and has * no gradient, and will use a DIV if that is the case. It will also check * if is true, which is the default settings. * Subclassers can either override or this function if the * result depends on dynamic values. The graph's dialect is available via * . */ mxShape.prototype.isMixedModeHtml = function() { return this.mixedModeHtml && !this.isRounded && !this.isShadow && this.gradient == null && mxUtils.getValue(this.style, mxConstants.STYLE_GLASS, 0) == 0 && mxUtils.getValue(this.style, mxConstants.STYLE_ROTATION, 0) == 0; }; /** * Function: create * * Creates and returns the DOM node(s) for the shape in * the given container. This implementation invokes * , or depending * on the and style settings. * * Parameters: * * container - DOM node that will contain the shape. */ mxShape.prototype.create = function(container) { var node = null; if (this.dialect == mxConstants.DIALECT_SVG) { node = this.createSvg(); } else if (this.dialect == mxConstants.DIALECT_STRICTHTML || (this.preferModeHtml && this.dialect == mxConstants.DIALECT_PREFERHTML) || (this.isMixedModeHtml() && this.dialect == mxConstants.DIALECT_MIXEDHTML)) { node = this.createHtml(); } else { node = this.createVml(); } return node; }; /** * Function: createHtml * * Creates and returns the HTML DOM node(s) to represent * this shape. This implementation falls back to * so that the HTML creation is optional. */ mxShape.prototype.createHtml = function() { var node = document.createElement('DIV'); this.configureHtmlShape(node); return node; }; /** * Function: destroy * * Destroys the shape by removing it from the DOM and releasing the DOM * node associated with the shape using . */ mxShape.prototype.destroy = function() { if (this.node != null) { mxEvent.release(this.node); if (this.node.parentNode != null) { this.node.parentNode.removeChild(this.node); } if (this.node.glassOverlay) { this.node.glassOverlay.parentNode.removeChild(this.node.glassOverlay); this.node.glassOverlay = null; } this.node = null; } }; /** * Function: apply * * Applies the style of the given to the shape. This * implementation assigns the following styles to local fields: * * - => fill * - => gradient * - => gradientDirection * - => opacity * - => stroke * - => strokewidth * - => isShadow * - => isDashed * - => spacing * - => startSize * - => endSize * - => isRounded * - => startArrow * - => endArrow * - => rotation * - => direction * * This keeps a reference to the '); }; /** * Function: createPageSelector * * Creates the page selector table. */ mxPrintPreview.prototype.createPageSelector = function(vpages, hpages) { var doc = this.wnd.document; var table = doc.createElement('table'); table.className = 'mxPageSelector'; table.setAttribute('border', '0'); var tbody = doc.createElement('tbody'); for (var i = 0; i < vpages; i++) { var row = doc.createElement('tr'); for (var j = 0; j < hpages; j++) { var pageNum = i * hpages + j + 1; var cell = doc.createElement('td'); // Needs anchor for all browers to work without JavaScript // LATER: Does not work in Firefox because the generated document // has the URL of the opening document, the anchor is appended // to that URL and the full URL is loaded on click. if (!mxClient.IS_NS || mxClient.IS_SF || mxClient.IS_GC) { var a = doc.createElement('a'); a.setAttribute('href', '#mxPage-' + pageNum); mxUtils.write(a, pageNum, doc); cell.appendChild(a); } else { mxUtils.write(cell, pageNum, doc); } row.appendChild(cell); } tbody.appendChild(row); } table.appendChild(tbody); return table; }; /** * Function: renderPage * * Creates a DIV that prints a single page of the given * graph using the given scale and returns the DIV that * represents the page. * * Parameters: * * w - Width of the page in pixels. * h - Height of the page in pixels. * dx - Horizontal translation for the diagram. * dy - Vertical translation for the diagram. * scale - Scale for the diagram. * pageNumber - Number of the page to be rendered. */ mxPrintPreview.prototype.renderPage = function(w, h, dx, dy, scale, pageNumber) { var div = document.createElement('div'); try { div.style.width = w + 'px'; div.style.height = h + 'px'; div.style.overflow = 'hidden'; div.style.pageBreakInside = 'avoid'; var innerDiv = document.createElement('div'); innerDiv.style.top = this.border + 'px'; innerDiv.style.left = this.border + 'px'; innerDiv.style.width = (w - 2 * this.border) + 'px'; innerDiv.style.height = (h - 2 * this.border) + 'px'; innerDiv.style.overflow = 'hidden'; if (this.graph.dialect == mxConstants.DIALECT_VML) { innerDiv.style.position = 'absolute'; } div.appendChild(innerDiv); document.body.appendChild(div); var view = this.graph.getView(); var previousContainer = this.graph.container; this.graph.container = innerDiv; var canvas = view.getCanvas(); var backgroundPane = view.getBackgroundPane(); var drawPane = view.getDrawPane(); var overlayPane = view.getOverlayPane(); if (this.graph.dialect == mxConstants.DIALECT_SVG) { view.createSvg(); } else if (this.graph.dialect == mxConstants.DIALECT_VML) { view.createVml(); } else { view.createHtml(); } // Disables events on the view var eventsEnabled = view.isEventsEnabled(); view.setEventsEnabled(false); // Disables the graph to avoid cursors var graphEnabled = this.graph.isEnabled(); this.graph.setEnabled(false); // Resets the translation var translate = view.getTranslate(); view.translate = new mxPoint(dx, dy); var temp = null; try { // Creates the temporary cell states in the view and // draws them onto the temporary DOM nodes in the view var model = this.graph.getModel(); var cells = [model.getRoot()]; temp = new mxTemporaryCellStates(view, scale, cells); } finally { // Removes overlay pane with selection handles // controls and icons from the print output if (mxClient.IS_IE) { view.overlayPane.innerHTML = ''; } else { // Removes everything but the SVG node var tmp = innerDiv.firstChild; while (tmp != null) { var next = tmp.nextSibling; var name = tmp.nodeName.toLowerCase(); // Note: Width and heigh are required in FF 11 if (name == 'svg') { tmp.setAttribute('width', parseInt(innerDiv.style.width)); tmp.setAttribute('height', parseInt(innerDiv.style.height)); } // Tries to fetch all text labels and only text labels else if (tmp.style.cursor != 'default' && name != 'table') { tmp.parentNode.removeChild(tmp); } tmp = next; } } // Completely removes the overlay pane to remove more handles view.overlayPane.parentNode.removeChild(view.overlayPane); // Restores the state of the view this.graph.setEnabled(graphEnabled); this.graph.container = previousContainer; view.canvas = canvas; view.backgroundPane = backgroundPane; view.drawPane = drawPane; view.overlayPane = overlayPane; view.translate = translate; temp.destroy(); view.setEventsEnabled(eventsEnabled); } } catch (e) { div.parentNode.removeChild(div); div = null; throw e; } return div; }; /** * Function: print * * Opens the print preview and shows the print dialog. */ mxPrintPreview.prototype.print = function() { var wnd = this.open(); if (wnd != null) { wnd.print(); } }; /** * Function: close * * Closes the print preview window. */ mxPrintPreview.prototype.close = function() { if (this.wnd != null) { this.wnd.close(); this.wnd = null; } }; /** * $Id: mxStylesheet.js,v 1.35 2010-03-26 10:24:58 gaudenz Exp $ * Copyright (c) 2006-2010, JGraph Ltd */ /** * Class: mxStylesheet * * Defines the appearance of the cells in a graph. See for an * example of creating a new cell style. It is recommended to use objects, not * arrays for holding cell styles. Existing styles can be cloned using * and turned into a string for debugging using * . * * Default Styles: * * The stylesheet contains two built-in styles, which are used if no style is * defined for a cell: * * defaultVertex - Default style for vertices * defaultEdge - Default style for edges * * Example: * * (code) * var vertexStyle = stylesheet.getDefaultVertexStyle(); * vertexStyle[mxConstants.ROUNDED] = true; * var edgeStyle = stylesheet.getDefaultEdgeStyle(); * edgeStyle[mxConstants.STYLE_EDGE] = mxEdgeStyle.EntityRelation; * (end) * * Modifies the built-in default styles. * * To avoid the default style for a cell, add a leading semicolon * to the style definition, eg. * * (code) * ;shadow=1 * (end) * * Removing keys: * * For removing a key in a cell style of the form [stylename;|key=value;] the * special value none can be used, eg. highlight;fillColor=none * * See also the helper methods in mxUtils to modify strings of this format, * namely , , * , , * and . * * Constructor: mxStylesheet * * Constructs a new stylesheet and assigns default styles. */ function mxStylesheet() { this.styles = new Object(); this.putDefaultVertexStyle(this.createDefaultVertexStyle()); this.putDefaultEdgeStyle(this.createDefaultEdgeStyle()); }; /** * Function: styles * * Maps from names to cell styles. Each cell style is a map of key, * value pairs. */ mxStylesheet.prototype.styles; /** * Function: createDefaultVertexStyle * * Creates and returns the default vertex style. */ mxStylesheet.prototype.createDefaultVertexStyle = function() { var style = new Object(); style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE; style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter; style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE; style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER; style[mxConstants.STYLE_FILLCOLOR] = '#C3D9FF'; style[mxConstants.STYLE_STROKECOLOR] = '#6482B9'; style[mxConstants.STYLE_FONTCOLOR] = '#774400'; return style; }; /** * Function: createDefaultEdgeStyle * * Creates and returns the default edge style. */ mxStylesheet.prototype.createDefaultEdgeStyle = function() { var style = new Object(); style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_CONNECTOR; style[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC; style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE; style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER; style[mxConstants.STYLE_STROKECOLOR] = '#6482B9'; style[mxConstants.STYLE_FONTCOLOR] = '#446299'; return style; }; /** * Function: putDefaultVertexStyle * * Sets the default style for vertices using defaultVertex as the * stylename. * * Parameters: * style - Key, value pairs that define the style. */ mxStylesheet.prototype.putDefaultVertexStyle = function(style) { this.putCellStyle('defaultVertex', style); }; /** * Function: putDefaultEdgeStyle * * Sets the default style for edges using defaultEdge as the stylename. */ mxStylesheet.prototype.putDefaultEdgeStyle = function(style) { this.putCellStyle('defaultEdge', style); }; /** * Function: getDefaultVertexStyle * * Returns the default style for vertices. */ mxStylesheet.prototype.getDefaultVertexStyle = function() { return this.styles['defaultVertex']; }; /** * Function: getDefaultEdgeStyle * * Sets the default style for edges. */ mxStylesheet.prototype.getDefaultEdgeStyle = function() { return this.styles['defaultEdge']; }; /** * Function: putCellStyle * * Stores the given map of key, value pairs under the given name in * . * * Example: * * The following example adds a new style called 'rounded' into an * existing stylesheet: * * (code) * var style = new Object(); * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE; * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter; * style[mxConstants.STYLE_ROUNDED] = true; * graph.getStylesheet().putCellStyle('rounded', style); * (end) * * In the above example, the new style is an object. The possible keys of * the object are all the constants in that start with STYLE * and the values are either JavaScript objects, such as * (which is in fact a function) * or expressions, such as true. Note that not all keys will be * interpreted by all shapes (eg. the line shape ignores the fill color). * The final call to this method associates the style with a name in the * stylesheet. The style is used in a cell with the following code: * * (code) * model.setStyle(cell, 'rounded'); * (end) * * Parameters: * * name - Name for the style to be stored. * style - Key, value pairs that define the style. */ mxStylesheet.prototype.putCellStyle = function(name, style) { this.styles[name] = style; }; /** * Function: getCellStyle * * Returns the cell style for the specified stylename or the given * defaultStyle if no style can be found for the given stylename. * * Parameters: * * name - String of the form [(stylename|key=value);] that represents the * style. * defaultStyle - Default style to be returned if no style can be found. */ mxStylesheet.prototype.getCellStyle = function(name, defaultStyle) { var style = defaultStyle; if (name != null && name.length > 0) { var pairs = name.split(';'); if (style != null && name.charAt(0) != ';') { style = mxUtils.clone(style); } else { style = new Object(); } // Parses each key, value pair into the existing style for (var i = 0; i < pairs.length; i++) { var tmp = pairs[i]; var pos = tmp.indexOf('='); if (pos >= 0) { var key = tmp.substring(0, pos); var value = tmp.substring(pos + 1); if (value == mxConstants.NONE) { delete style[key]; } else if (mxUtils.isNumeric(value)) { style[key] = parseFloat(value); } else { style[key] = value; } } else { // Merges the entries from a named style var tmpStyle = this.styles[tmp]; if (tmpStyle != null) { for (var key in tmpStyle) { style[key] = tmpStyle[key]; } } } } } return style; }; /** * $Id: mxCellState.js,v 1.42 2012-03-19 10:47:08 gaudenz Exp $ * Copyright (c) 2006-2010, JGraph Ltd */ /** * Class: mxCellState * * Represents the current state of a cell in a given . * * For edges, the edge label position is stored in . * * The size for oversize labels can be retrieved using the boundingBox property * of the field as shown below. * * (code) * var bbox = (state.text != null) ? state.text.boundingBox : null; * (end) * * Constructor: mxCellState * * Constructs a new object that represents the current state of the given * cell in the specified view. * * Parameters: * * view - that contains the state. * cell - that this state represents. * style - Array of key, value pairs that constitute the style. */ function mxCellState(view, cell, style) { this.view = view; this.cell = cell; this.style = style; this.origin = new mxPoint(); this.absoluteOffset = new mxPoint(); }; /** * Extends mxRectangle. */ mxCellState.prototype = new mxRectangle(); mxCellState.prototype.constructor = mxCellState; /** * Variable: view * * Reference to the enclosing . */ mxCellState.prototype.view = null; /** * Variable: cell * * Reference to the that is represented by this state. */ mxCellState.prototype.cell = null; /** * Variable: style * * Contains an array of key, value pairs that represent the style of the * cell. */ mxCellState.prototype.style = null; /** * Variable: invalid * * Specifies if the state is invalid. Default is true. */ mxCellState.prototype.invalid = true; /** * Variable: invalidOrder * * Specifies if the cell has an invalid order. For internal use. Default is * false. */ mxCellState.prototype.invalidOrder = false; /** * Variable: orderChanged * * Specifies if the cell has changed order and the display needs to be * updated. */ mxCellState.prototype.orderChanged = false; /** * Variable: origin * * that holds the origin for all child cells. Default is a new * empty . */ mxCellState.prototype.origin = null; /** * Variable: absolutePoints * * Holds an array of that represent the absolute points of an * edge. */ mxCellState.prototype.absolutePoints = null; /** * Variable: absoluteOffset * * that holds the absolute offset. For edges, this is the * absolute coordinates of the label position. For vertices, this is the * offset of the label relative to the top, left corner of the vertex. */ mxCellState.prototype.absoluteOffset = null; /** * Variable: visibleSourceState * * Caches the visible source terminal state. */ mxCellState.prototype.visibleSourceState = null; /** * Variable: visibleTargetState * * Caches the visible target terminal state. */ mxCellState.prototype.visibleTargetState = null; /** * Variable: terminalDistance * * Caches the distance between the end points for an edge. */ mxCellState.prototype.terminalDistance = 0; /** * Variable: length * * Caches the length of an edge. */ mxCellState.prototype.length = 0; /** * Variable: segments * * Array of numbers that represent the cached length of each segment of the * edge. */ mxCellState.prototype.segments = null; /** * Variable: shape * * Holds the that represents the cell graphically. */ mxCellState.prototype.shape = null; /** * Variable: text * * Holds the that represents the label of the cell. Thi smay be * null if the cell has no label. */ mxCellState.prototype.text = null; /** * Function: getPerimeterBounds * * Returns the that should be used as the perimeter of the * cell. * * Parameters: * * border - Optional border to be added around the perimeter bounds. * bounds - Optional to be used as the initial bounds. */ mxCellState.prototype.getPerimeterBounds = function (border, bounds) { border = border || 0; bounds = (bounds != null) ? bounds : new mxRectangle(this.x, this.y, this.width, this.height); if (this.shape != null && this.shape.stencil != null) { var aspect = this.shape.stencil.computeAspect(this, bounds, null); bounds.x = aspect.x; bounds.y = aspect.y; bounds.width = this.shape.stencil.w0 * aspect.width; bounds.height = this.shape.stencil.h0 * aspect.height; } if (border != 0) { bounds.grow(border); } return bounds; }; /** * Function: setAbsoluteTerminalPoint * * Sets the first or last point in depending on isSource. * * Parameters: * * point - that represents the terminal point. * isSource - Boolean that specifies if the first or last point should * be assigned. */ mxCellState.prototype.setAbsoluteTerminalPoint = function (point, isSource) { if (isSource) { if (this.absolutePoints == null) { this.absolutePoints = []; } if (this.absolutePoints.length == 0) { this.absolutePoints.push(point); } else { this.absolutePoints[0] = point; } } else { if (this.absolutePoints == null) { this.absolutePoints = []; this.absolutePoints.push(null); this.absolutePoints.push(point); } else if (this.absolutePoints.length == 1) { this.absolutePoints.push(point); } else { this.absolutePoints[this.absolutePoints.length - 1] = point; } } }; /** * Function: setCursor * * Sets the given cursor on the shape and text shape. */ mxCellState.prototype.setCursor = function (cursor) { if (this.shape != null) { this.shape.setCursor(cursor); } if (this.text != null) { this.text.setCursor(cursor); } }; /** * Function: getVisibleTerminal * * Returns the visible source or target terminal cell. * * Parameters: * * source - Boolean that specifies if the source or target cell should be * returned. */ mxCellState.prototype.getVisibleTerminal = function (source) { var tmp = this.getVisibleTerminalState(source); return (tmp != null) ? tmp.cell : null; }; /** * Function: getVisibleTerminalState * * Returns the visible source or target terminal state. * * Parameters: * * source - Boolean that specifies if the source or target state should be * returned. */ mxCellState.prototype.getVisibleTerminalState = function (source) { return (source) ? this.visibleSourceState : this.visibleTargetState; }; /** * Function: setVisibleTerminalState * * Sets the visible source or target terminal state. * * Parameters: * * terminalState - that represents the terminal. * source - Boolean that specifies if the source or target state should be set. */ mxCellState.prototype.setVisibleTerminalState = function (terminalState, source) { if (source) { this.visibleSourceState = terminalState; } else { this.visibleTargetState = terminalState; } }; /** * Destructor: destroy * * Destroys the state and all associated resources. */ mxCellState.prototype.destroy = function () { this.view.graph.cellRenderer.destroy(this); }; /** * Function: clone * * Returns a clone of this . */ mxCellState.prototype.clone = function() { var clone = new mxCellState(this.view, this.cell, this.style); // Clones the absolute points if (this.absolutePoints != null) { clone.absolutePoints = []; for (var i = 0; i < this.absolutePoints.length; i++) { clone.absolutePoints[i] = this.absolutePoints[i].clone(); } } if (this.origin != null) { clone.origin = this.origin.clone(); } if (this.absoluteOffset != null) { clone.absoluteOffset = this.absoluteOffset.clone(); } if (this.boundingBox != null) { clone.boundingBox = this.boundingBox.clone(); } clone.terminalDistance = this.terminalDistance; clone.segments = this.segments; clone.length = this.length; clone.x = this.x; clone.y = this.y; clone.width = this.width; clone.height = this.height; return clone; }; /** * $Id: mxGraphSelectionModel.js,v 1.14 2011-11-25 10:16:08 gaudenz Exp $ * Copyright (c) 2006-2010, JGraph Ltd */ /** * Class: mxGraphSelectionModel * * Implements the selection model for a graph. Here is a listener that handles * all removed selection cells. * * (code) * graph.getSelectionModel().addListener(mxEvent.CHANGE, function(sender, evt) * { * var cells = evt.getProperty('added'); * * for (var i = 0; i < cells.length; i++) * { * // Handle cells[i]... * } * }); * (end) * * Event: mxEvent.UNDO * * Fires after the selection was changed in . The * edit property contains the which contains the * . * * Event: mxEvent.CHANGE * * Fires after the selection changes by executing an . The * added and removed properties contain arrays of * cells that have been added to or removed from the selection, respectively. * * Constructor: mxGraphSelectionModel * * Constructs a new graph selection model for the given . * * Parameters: * * graph - Reference to the enclosing . */ function mxGraphSelectionModel(graph) { this.graph = graph; this.cells = []; }; /** * Extends mxEventSource. */ mxGraphSelectionModel.prototype = new mxEventSource(); mxGraphSelectionModel.prototype.constructor = mxGraphSelectionModel; /** * Variable: doneResource * * Specifies the resource key for the status message after a long operation. * If the resource for this key does not exist then the value is used as * the status message. Default is 'done'. */ mxGraphSelectionModel.prototype.doneResource = (mxClient.language != 'none') ? 'done' : ''; /** * Variable: updatingSelectionResource * * Specifies the resource key for the status message while the selection is * being updated. If the resource for this key does not exist then the * value is used as the status message. Default is 'updatingSelection'. */ mxGraphSelectionModel.prototype.updatingSelectionResource = (mxClient.language != 'none') ? 'updatingSelection' : ''; /** * Variable: graph * * Reference to the enclosing . */ mxGraphSelectionModel.prototype.graph = null; /** * Variable: singleSelection * * Specifies if only one selected item at a time is allowed. * Default is false. */ mxGraphSelectionModel.prototype.singleSelection = false; /** * Function: isSingleSelection * * Returns as a boolean. */ mxGraphSelectionModel.prototype.isSingleSelection = function() { return this.singleSelection; }; /** * Function: setSingleSelection * * Sets the flag. * * Parameters: * * singleSelection - Boolean that specifies the new value for * . */ mxGraphSelectionModel.prototype.setSingleSelection = function(singleSelection) { this.singleSelection = singleSelection; }; /** * Function: isSelected * * Returns true if the given is selected. */ mxGraphSelectionModel.prototype.isSelected = function(cell) { if (cell != null) { return mxUtils.indexOf(this.cells, cell) >= 0; } return false; }; /** * Function: isEmpty * * Returns true if no cells are currently selected. */ mxGraphSelectionModel.prototype.isEmpty = function() { return this.cells.length == 0; }; /** * Function: clear * * Clears the selection and fires a event if the selection was not * empty. */ mxGraphSelectionModel.prototype.clear = function() { this.changeSelection(null, this.cells); }; /** * Function: setCell * * Selects the specified using . * * Parameters: * * cell - to be selected. */ mxGraphSelectionModel.prototype.setCell = function(cell) { if (cell != null) { this.setCells([cell]); } }; /** * Function: setCells * * Selects the given array of and fires a event. * * Parameters: * * cells - Array of to be selected. */ mxGraphSelectionModel.prototype.setCells = function(cells) { if (cells != null) { if (this.singleSelection) { cells = [this.getFirstSelectableCell(cells)]; } var tmp = []; for (var i = 0; i < cells.length; i++) { if (this.graph.isCellSelectable(cells[i])) { tmp.push(cells[i]); } } this.changeSelection(tmp, this.cells); } }; /** * Function: getFirstSelectableCell * * Returns the first selectable cell in the given array of cells. */ mxGraphSelectionModel.prototype.getFirstSelectableCell = function(cells) { if (cells != null) { for (var i = 0; i < cells.length; i++) { if (this.graph.isCellSelectable(cells[i])) { return cells[i]; } } } return null; }; /** * Function: addCell * * Adds the given to the selection and fires a * event. * * Parameters: * * cells - Array of to add to the selection. */ mxGraphSelectionModel.prototype.addCells = function(cells) { if (cells != null) { var remove = null; if (this.singleSelection) { remove = this.cells; cells = [this.getFirstSelectableCell(cells)]; } var tmp = []; for (var i = 0; i < cells.length; i++) { if (!this.isSelected(cells[i]) && this.graph.isCellSelectable(cells[i])) { tmp.push(cells[i]); } } this.changeSelection(tmp, remove); } }; /** * Function: removeCell * * Removes the specified from the selection and fires a