summaryrefslogtreecommitdiff
path: root/src/js/util/mxUtils.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/js/util/mxUtils.js')
-rw-r--r--src/js/util/mxUtils.js3920
1 files changed, 3920 insertions, 0 deletions
diff --git a/src/js/util/mxUtils.js b/src/js/util/mxUtils.js
new file mode 100644
index 0000000..34c0318
--- /dev/null
+++ b/src/js/util/mxUtils.js
@@ -0,0 +1,3920 @@
+/**
+ * $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 <open>,
+ * <save>, <saveAs> and <copy> 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 - <mxGraph> to be repainted.
+ * pt - <mxPoint> 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
+ * <mxConstants.NODETYPE_ELEMENT>.
+ */
+ 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(
+ * '<mxGraphModel><root><MyDiagram id="0"><mxCell/></MyDiagram>'+
+ * '<MyLayer id="1"><mxCell parent="0" /></MyLayer><MyObject id="2">'+
+ * '<mxCell style="strokeColor=blue;fillColor=red" parent="1" vertex="1">'+
+ * '<mxGeometry x="10" y="10" width="80" height="30" as="geometry"/>'+
+ * '</mxCell></MyObject></root></mxGraphModel>');
+ * (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 <getXml> 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 + '</'+node.nodeName+'>\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,'&amp;'); // 38 26
+ s = s.replace(/"/g,'&quot;'); // 34 22
+ s = s.replace(/\'/g,'&#39;'); // 39 27
+ s = s.replace(/</g,'&lt;'); // 60 3C
+ s = s.replace(/>/g,'&gt;'); // 62 3E
+
+ if (newline == null || newline)
+ {
+ s = s.replace(/\n/g, '&#xa;');
+ }
+
+ 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 &#xa; if
+ * no linefeed is defined.
+ *
+ * Parameters:
+ *
+ * node - DOM node to return the XML for.
+ * linefeed - Optional string that linefeeds are converted into. Default is
+ * &#xa;
+ */
+ 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 || '&#xa;';
+ 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('</'+node.nodeName+'>');
+ }
+
+ 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 - <mxEditor> 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 - <mxEditor> 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 <mxXmlRequest>.
+ * Throws an exception if the file cannot be loaded. See <mxUtils.get> 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 <mxXmlRequest> in use. Both
+ * functions take the <mxXmlRequest> as the only parameter. See
+ * <mxUtils.load> 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
+ * <mxXmlRequest> in use. Both functions take the <mxXmlRequest> 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
+ * <mxXmlRequest.simulate> and returns the <mxXmlRequest>.
+ * 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 <mxXmlRequest>, 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 <mxConstants.NONE> 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. <mxObjectIdentity.FIELD_NAME> 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 <mxPoints> to be compared.
+ * b - Array of <mxPoints> 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 - <mxRectangle> to be compared.
+ * b - <mxRectangle> 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 - <mxCelState> that represents the terminal.
+ * edge - <mxCellState> 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 - <mxRectangle> that represents the rectangle.
+ * p1 - <mxPoint> that represents the first point of the segment.
+ * p2 - <mxPoint> 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 - <mxRectangle> 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 - <mxRectangle> to be checked for intersection.
+ * b - <mxRectangle> 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 - <mxRectangle> to be checked for intersection.
+ * b - <mxRectangle> 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 <mxPoint>. 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 <mxPoint>.
+ */
+ 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 <mxPoint> 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 <mxPoint>.
+ *
+ * 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 <mxEffects.animateChanges>. 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 <mxEffects.cascadeOpacity>. This is for backwards compatibility and
+ * will be removed later.
+ */
+ cascadeOpacity: function(graph, cell, opacity)
+ {
+ mxEffects.cascadeOpacity.apply(this, arguments);
+ },
+
+ /**
+ * Function: fadeOut
+ *
+ * See <mxEffects.fadeOut>. 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 - <mxGraphModel> to execute the transaction in.
+ * cells - Array of <mxCells> 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 - <mxGraphModel> that contains the cells.
+ * cells - Array of <mxCells> 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 <mxRectangle> with the size (width and height in pixels) of
+ * the given string. The string may contain HTML markup. Newlines should be
+ * converted to <br> before calling this method.
+ *
+ * Example:
+ *
+ * (code)
+ * var label = graph.getLabel(cell).replace(/\n/g, "<br>");
+ * 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
+ * <mxConstants.DEFAULT_FONTSIZE>.
+ * fontFamily - String that specifies the name of the font family. Default
+ * is <mxConstants.DEFAULT_FONTFAMILY>.
+ */
+ 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 <mxPrintPreview> for an example.
+ *
+ * Parameters:
+ *
+ * pageCount - Specifies the number of pages in the print output.
+ * graph - <mxGraph> that should be printed.
+ * pageFormat - Optional <mxRectangle> that specifies the page format.
+ * Default is <mxConstants.PAGE_FORMAT_A4_PORTRAIT>.
+ * 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 - <mxGraph> 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>';
+ html += '<head>';
+
+ var base = document.getElementsByTagName('base');
+
+ for (var i = 0; i < base.length; i++)
+ {
+ html += base[i].outerHTML;
+ }
+
+ html += '<style>';
+
+ // Copies the stylesheets without having to load them again
+ for (var i = 0; i < document.styleSheets.length; i++)
+ {
+ try
+ {
+ html += document.styleSheets(i).cssText;
+ }
+ catch (e)
+ {
+ // ignore security exception
+ }
+ }
+
+ html += '</style>';
+
+ html += '</head>';
+ html += '<body>';
+
+ // Copies the contents of the graph container
+ html += graph.container.innerHTML;
+
+ html += '</body>';
+ 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('<html');
+ doc.writeln('<head>');
+
+ var base = document.getElementsByTagName('base');
+
+ for (var i=0; i<base.length; i++)
+ {
+ doc.writeln(mxUtils.getOuterHtml(base[i]));
+ }
+
+ var links = document.getElementsByTagName('link');
+
+ for (var i=0; i<links.length; i++)
+ {
+ doc.writeln(mxUtils.getOuterHtml(links[i]));
+ }
+
+ var styles = document.getElementsByTagName('style');
+
+ for (var i=0; i<styles.length; i++)
+ {
+ doc.writeln(mxUtils.getOuterHtml(styles[i]));
+ }
+
+ doc.writeln('</head>');
+ doc.writeln('</html>');
+ 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 - <mxGraph> 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 <mxWindow> 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,'<br>').replace(/ /g, '&nbsp;');
+
+ 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('<pre>'+mxUtils.htmlEntities(content)+'</pre');
+ wnd.document.close();
+ }
+ else
+ {
+ var wnd = window.open();
+ var pre = wnd.document.createElement('pre');
+ pre.innerHTML = mxUtils.htmlEntities(content, false).
+ replace(/\n/g,'<br>').replace(/ /g, '&nbsp;');
+ 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 <mxWindow> 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 <mxUtils.errorImage>.
+ *
+ * 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')); // &nbsp;
+ div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
+ div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
+ 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 <mxDragSource>. If
+ * <mxDragSource.guideEnabled> 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 - <mxGraph> that acts as the drop target or a function that takes a
+ * mouse event and returns the current <mxGraph>.
+ * 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;
+ }
+
+};