diff --git a/Kieker.WebGUI/bin/data/Bookstore-Example/Bookstore-Example.kax b/Kieker.WebGUI/bin/data/Bookstore-Example/Bookstore-Example.kax index 23dbdb895defd1819afc273b49ff4117d386a6d7..cf369d16d1b150c42762921759d3c14cbd070999 100644 --- a/Kieker.WebGUI/bin/data/Bookstore-Example/Bookstore-Example.kax +++ b/Kieker.WebGUI/bin/data/Bookstore-Example/Bookstore-Example.kax @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <Project xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="platform:/resource/Kieker/model/AnalysisMetaModel.ecore"> - <plugins xsi:type="Filter" name="FSReader" classname="kieker.analysis.plugin.reader.filesystem.FSReader"> + <plugins xsi:type="Reader" name="FSReader" classname="kieker.analysis.plugin.reader.filesystem.FSReader"> <properties name="inputDirs" value="testdata"/> <properties name="ignoreUnknownRecordTypes" value="false"/> <outputPorts name="monitoringRecords" subscribers="//@plugins.1/@inputPorts.0 //@plugins.4/@inputPorts.0"/> diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorBean.java b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorBean.java index be7547bd5e5a23f0c786bf632e13cb149a975f46..0b435a3e3ad0ef71faac3e2c0002d7765df6ec1d 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorBean.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorBean.java @@ -58,6 +58,7 @@ import kieker.analysis.repository.AbstractRepository; import kieker.common.logging.Log; import kieker.common.logging.LogFactory; import kieker.webgui.beans.application.ProjectsBean; +import kieker.webgui.common.ClassAndMethodContainer; import kieker.webgui.common.FSManager; import kieker.webgui.common.Pair; import kieker.webgui.common.PluginFinder; @@ -867,7 +868,7 @@ public final class CurrentAnalysisEditorBean { public void addPlugin(final Class<AbstractPlugin> clazz) { // Create a new instance for the model final MIPlugin plugin; - if (AbstractReaderPlugin.class.isAssignableFrom(clazz)) { + if (this.classAndMethodContainer.getAbstractReaderPluginClass().isAssignableFrom(clazz)) { plugin = this.factory.createReader(); } else { plugin = this.factory.createFilter(); diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/ClassAndMethodContainer.java b/Kieker.WebGUI/src/main/java/kieker/webgui/common/ClassAndMethodContainer.java similarity index 51% rename from Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/ClassAndMethodContainer.java rename to Kieker.WebGUI/src/main/java/kieker/webgui/common/ClassAndMethodContainer.java index 7bf0e6890e3b573a2101d1e41d7d417434cc0bfd..ce18dee4f271f06bd8fba2dd7c18b9213ba6bcda 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/ClassAndMethodContainer.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/common/ClassAndMethodContainer.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. ***************************************************************************/ -package kieker.webgui.beans.view; +package kieker.webgui.common; import java.lang.annotation.Annotation; import java.lang.reflect.Method; @@ -37,34 +37,128 @@ import kieker.common.logging.Log; import kieker.common.logging.LogFactory; import kieker.webgui.common.exception.ProjectLoadException; +/** + * The {@link ClassAndMethodContainer} is a container which contains - as the name already suggests - various classes and methods. To be more precisely, it uses a + * given class loader to load the equivalence of specific classes within this application to ensure that comparisons, assignments and the use of specific methods + * will be done correctly. This is necessary as for every project within this application there will be a number of libraries which will be combined in one class + * loader. This will result in multiple version of one and the same class and therefore in problems, if one doesn't use the correct class version. + * + * @author Nils Christian Ehmke + * @version 1.0 + */ public final class ClassAndMethodContainer { + /** + * This is the logger, which will be used to log messages and exceptions for this class. + */ private static final Log LOG = LogFactory.getLog(ClassAndMethodContainer.class); - + /** + * The exception which is used if something went wrong during loading. + */ + private static final String MSG_LOAD_EXCEPTION = "An error occured while loading the classes and methods."; + + /** + * This is the class equivalence of {@link AbstractRepository}. + */ private final Class<?> abstractRepositoryClass; + /** + * This is the class equivalence of {@link AbstractPlugin}. + */ private final Class<?> abstractPluginClass; + /** + * This is the class equivalence of {@link AbstractFilterPlugin}. + */ private final Class<?> abstractFilterPluginClass; + /** + * This is the class equivalence of {@link AbstractRepository}. + */ private final Class<?> abstractReaderPluginClass; + /** + * This is the class equivalence of {@link Plugin}. + */ private final Class<? extends Annotation> pluginAnnotationClass; + /** + * This is the class equivalence of {@link Repository}. + */ private final Class<? extends Annotation> repositoryAnnotationClass; + /** + * This is the class equivalence of {@link Property}. + */ private final Class<? extends Annotation> propertyAnnotationClass; + /** + * This is the class equivalence of {@link OutputPort}. + */ private final Class<? extends Annotation> outputPortAnnotationClass; + /** + * This is the class equivalence of {@link InputPort}. + */ private final Class<? extends Annotation> inputPortAnnotationClass; + /** + * This is the class equivalence of {@link RepositoryPort}. + */ private final Class<? extends Annotation> repositoryPortAnnotationClass; + /** + * This is the class equivalence of {@link Display}. + */ private final Class<? extends Annotation> displayAnnotationClass; + /** + * This is the description()-method of the class equivalence of {@link Plugin}. + */ private final Method pluginDescriptionMethod; + /** + * This is the description()-method of the class equivalence of {@link Repository}. + */ private final Method repositoryDescriptionMethod; + /** + * This is the configuration()-method of the class equivalence of {@link Plugin}. + */ private final Method pluginConfigurationMethod; + /** + * This is the description()-method of the class equivalence of {@link Repository}. + */ private final Method repositoryConfigurationMethod; + /** + * This is the outputPorts()-method of the class equivalence of {@link Plugin}. + */ private final Method pluginOutputPortsMethod; + /** + * This is the repositoryPorts()-method of the class equivalence of {@link Plugin}. + */ private final Method pluginRepositoryPortsMethod; + /** + * This is the name()-method of the class equivalence of {@link Display}. + */ private final Method displayNameMethod; + /** + * This is the name()-method of the class equivalence of {@link InputPort}. + */ private final Method inputPortNameMethod; + /** + * This is the name()-method of the class equivalence of {@link OutputPort}. + */ private final Method outputPortNameMethod; + /** + * This is the name()-method of the class equivalence of {@link RepositoryPort}. + */ private final Method repositoryPortNameMethod; + /** + * This is the name()-method of the class equivalence of {@link Property}. + */ private final Method propertyNameMethod; + /** + * This is the defaultValue()-method of the class equivalence of {@link Property}. + */ private final Method propertyDefaultValueMethod; + /** + * Creates a new instance of this class, using the given class loader. + * + * @param classLoader + * The class loader which will be used to load all classes and determine their methods. + * @throws ProjectLoadException + * If one or more of the classes or methods for this container could not be found. + */ + @SuppressWarnings("unchecked") public ClassAndMethodContainer(final ClassLoader classLoader) throws ProjectLoadException { try { this.abstractFilterPluginClass = classLoader.loadClass(AbstractFilterPlugin.class.getCanonicalName()); @@ -93,106 +187,223 @@ public final class ClassAndMethodContainer { this.propertyNameMethod = this.propertyAnnotationClass.getMethod("name", new Class<?>[0]); this.propertyDefaultValueMethod = this.propertyAnnotationClass.getMethod("defaultValue", new Class<?>[0]); } catch (final ClassNotFoundException ex) { - ClassAndMethodContainer.LOG.error("An error occured while loading the classes and methods.", ex); - throw new ProjectLoadException(); + ClassAndMethodContainer.LOG.error(ClassAndMethodContainer.MSG_LOAD_EXCEPTION, ex); + throw new ProjectLoadException(ClassAndMethodContainer.MSG_LOAD_EXCEPTION, ex); } catch (final NoSuchMethodException ex) { - ClassAndMethodContainer.LOG.error("An error occured while loading the classes and methods.", ex); - throw new ProjectLoadException(); + ClassAndMethodContainer.LOG.error(ClassAndMethodContainer.MSG_LOAD_EXCEPTION, ex); + throw new ProjectLoadException(ClassAndMethodContainer.MSG_LOAD_EXCEPTION, ex); } catch (final SecurityException ex) { - ClassAndMethodContainer.LOG.error("An error occured while loading the classes and methods.", ex); - throw new ProjectLoadException(); + ClassAndMethodContainer.LOG.error(ClassAndMethodContainer.MSG_LOAD_EXCEPTION, ex); + throw new ProjectLoadException(ClassAndMethodContainer.MSG_LOAD_EXCEPTION, ex); + } catch (final ClassCastException ex) { + ClassAndMethodContainer.LOG.error(ClassAndMethodContainer.MSG_LOAD_EXCEPTION, ex); + throw new ProjectLoadException(ClassAndMethodContainer.MSG_LOAD_EXCEPTION, ex); } - } + /** + * The getter-method for the field {@link ClassAndMethodContainer#abstractFilterPluginClass}. + * + * @return The current value for the field. + */ public Class<?> getAbstractFilterPluginClass() { return this.abstractFilterPluginClass; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#abstractReaderPluginClass}. + * + * @return The current value for the field. + */ public Class<?> getAbstractReaderPluginClass() { return this.abstractReaderPluginClass; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#pluginAnnotationClass}. + * + * @return The current value for the field. + */ public Class<? extends Annotation> getPluginAnnotationClass() { return this.pluginAnnotationClass; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#repositoryAnnotationClass}. + * + * @return The current value for the field. + */ public Class<? extends Annotation> getRepositoryAnnotationClass() { return this.repositoryAnnotationClass; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#propertyAnnotationClass}. + * + * @return The current value for the field. + */ public Class<? extends Annotation> getPropertyAnnotationClass() { return this.propertyAnnotationClass; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#outputPortAnnotationClass}. + * + * @return The current value for the field. + */ public Class<? extends Annotation> getOutputPortAnnotationClass() { return this.outputPortAnnotationClass; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#inputPortAnnotationClass}. + * + * @return The current value for the field. + */ public Class<? extends Annotation> getInputPortAnnotationClass() { return this.inputPortAnnotationClass; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#repositoryPortAnnotationClass}. + * + * @return The current value for the field. + */ public Class<? extends Annotation> getRepositoryPortAnnotationClass() { return this.repositoryPortAnnotationClass; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#displayAnnotationClass}. + * + * @return The current value for the field. + */ public Class<? extends Annotation> getDisplayAnnotationClass() { return this.displayAnnotationClass; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#pluginDescriptionMethod}. + * + * @return The current value for the field. + */ public Method getPluginDescriptionMethod() { return this.pluginDescriptionMethod; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#repositoryDescriptionMethod}. + * + * @return The current value for the field. + */ public Method getRepositoryDescriptionMethod() { return this.repositoryDescriptionMethod; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#pluginConfigurationMethod}. + * + * @return The current value for the field. + */ public Method getPluginConfigurationMethod() { return this.pluginConfigurationMethod; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#repositoryConfigurationMethod}. + * + * @return The current value for the field. + */ public Method getRepositoryConfigurationMethod() { return this.repositoryConfigurationMethod; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#pluginOutputPortsMethod}. + * + * @return The current value for the field. + */ public Method getPluginOutputPortsMethod() { return this.pluginOutputPortsMethod; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#pluginRepositoryPortsMethod}. + * + * @return The current value for the field. + */ public Method getPluginRepositoryPortsMethod() { return this.pluginRepositoryPortsMethod; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#displayNameMethod}. + * + * @return The current value for the field. + */ public Method getDisplayNameMethod() { return this.displayNameMethod; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#abstractRepositoryClass}. + * + * @return The current value for the field. + */ public Class<?> getAbstractRepositoryClass() { return this.abstractRepositoryClass; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#abstractPluginClass}. + * + * @return The current value for the field. + */ public Class<?> getAbstractPluginClass() { return this.abstractPluginClass; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#inputPortNameMethod}. + * + * @return The current value for the field. + */ public Method getInputPortNameMethod() { return this.inputPortNameMethod; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#outputPortNameMethod}. + * + * @return The current value for the field. + */ public Method getOutputPortNameMethod() { return this.outputPortNameMethod; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#repositoryPortNameMethod}. + * + * @return The current value for the field. + */ public Method getRepositoryPortNameMethod() { return this.repositoryPortNameMethod; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#propertyNameMethod}. + * + * @return The current value for the field. + */ public Method getPropertyNameMethod() { return this.propertyNameMethod; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#propertyDefaultValueMethod}. + * + * @return The current value for the field. + */ public Method getPropertyDefaultValueMethod() { return this.propertyDefaultValueMethod; } diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/common/FSManager.java b/Kieker.WebGUI/src/main/java/kieker/webgui/common/FSManager.java index 7d1675b21ba5b84dc78137741fcf425798ad2b58..01d1053d600fbbbf404535aa22463e31026e3ed4 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/common/FSManager.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/common/FSManager.java @@ -84,6 +84,11 @@ public final class FSManager { // NOCS (Class Data Abstraction Coupling, Class F * This is the name of the root-directory. */ private static final String ROOT_DIRECTORY = "data"; + /** + * The library for kieker which is contained in the war-file as a ressource. + */ + private static final String KIEKER_LIB = "kieker-1.6-SNAPSHOT_emf.jar"; + /** * This is the buffer (in bytes) used to copy and upload files. */ @@ -663,8 +668,7 @@ public final class FSManager { // NOCS (Class Data Abstraction Coupling, Class F } // Add the kieker lib! - final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); - libs.add(contextClassLoader.getResource(Global.KIEKER_LIB)); + libs.add(this.getKiekerURL()); // Now assemble the URL class loader final PrivilegedClassLoaderAction action = new PrivilegedClassLoaderAction(libs); @@ -754,7 +758,7 @@ public final class FSManager { // NOCS (Class Data Abstraction Coupling, Class F } public URL getKiekerURL() { - return Thread.currentThread().getContextClassLoader().getResource(Global.KIEKER_LIB); + return Thread.currentThread().getContextClassLoader().getResource(FSManager.KIEKER_LIB); } } diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/common/Global.java b/Kieker.WebGUI/src/main/java/kieker/webgui/common/Global.java index f95d34500921678ef5e49f44a5d222da6a098c32..e37c6f9ee76111086fd6813c32b25941eda25374 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/common/Global.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/common/Global.java @@ -33,8 +33,6 @@ public final class Global { */ public static final String PAGE_PROJECT_OVERVIEW = "ProjectOverview.xhtml?faces-redirect=true"; - public static final String KIEKER_LIB = "kieker-1.6-SNAPSHOT_emf.jar"; - /** * Default constructor. */ diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/common/PluginFinder.java b/Kieker.WebGUI/src/main/java/kieker/webgui/common/PluginFinder.java index e2e2dbbe942fb35ba3a058c4a213c03e363b87d7..7a987557970c813bbc3bc330c31cf65bd5d7a85b 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/common/PluginFinder.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/common/PluginFinder.java @@ -26,7 +26,6 @@ import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; -import kieker.webgui.beans.view.ClassAndMethodContainer; import kieker.webgui.common.exception.ProjectLoadException; /** diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/common/exception/ProjectLoadException.java b/Kieker.WebGUI/src/main/java/kieker/webgui/common/exception/ProjectLoadException.java index 3937564d3a0b428c6abb81a68b1b72e82f8953e3..e72132be79a7a790c106a0f6df5b3373636665ce 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/common/exception/ProjectLoadException.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/common/exception/ProjectLoadException.java @@ -41,4 +41,16 @@ public class ProjectLoadException extends Exception { public ProjectLoadException(final String msg) { super(msg); } + + /** + * Creates a new instance of this class using the given parameters. + * + * @param msg + * The message used for the exception. + * @param The + * cause for the method. + */ + public ProjectLoadException(final String msg, final Throwable cause) { + super(msg, cause); + } } diff --git a/Kieker.WebGUI/src/main/webapp/js/js-graph-it.js b/Kieker.WebGUI/src/main/webapp/js/js-graph-it.js deleted file mode 100644 index 9482cc8d7ce5ad129ea4bb270377e04c70fae40c..0000000000000000000000000000000000000000 --- a/Kieker.WebGUI/src/main/webapp/js/js-graph-it.js +++ /dev/null @@ -1,1801 +0,0 @@ -/********************* - * browser detection * - *********************/ - -var ie=document.all; -var nn6=document.getElementById&&!document.all; - -/**************************************************** - * This class is a scanner for the visitor pattern. * - ****************************************************/ - -/** - * Constructor, parameters are: - * visitor: the visitor implementation, it must be a class with a visit(element) method. - * scanElementsOnly: a flag telling whether to scan html elements only or all html nodes. - */ -function DocumentScanner(visitor, scanElementsOnly) -{ - this.visitor = visitor; - this.scanElementsOnly = scanElementsOnly; - - /** - * Scans the element - */ - this.scan = function(element) - { - var i; - if(this.visitor.visit(element)) - { - // visit child elements - var children = element.childNodes; - for(i = 0; i < children.length; i++) - { - if(!this.scanElementsOnly || children[i].nodeType == 1) - { - this.scan(children[i]); - } - } - } - } -} - -/***************** - * drag and drop * - *****************/ - -var isdrag=false; // this flag indicates that the mouse movement is actually a drag. -var mouseStartX, mouseStartY; // mouse position when drag starts -var elementStartX, elementStartY; // element position when drag starts - -/** - * the html element being dragged. - */ -var elementToMove; - -/** - * an array containing the blocks being dragged. This is used to notify them of move. - */ -var blocksToMove; - -/** - * this variable stores the orginal z-index of the object being dragged in order - * to restore it upon drop. - */ -var originalZIndex; - -/** - * an array containing bounds to be respected while dragging elements, - * these bounds are left, top, left + width, top + height of the parent element. - */ -var bounds = new Array(4); - -/** - * this visitor is used to find blocks nested in the element being moved. - */ -function BlocksToMoveVisitor() -{ - this.visit = function(element) - { - if(isBlock(element)) - { - blocksToMove.push(findBlock(element.id)); - return false; - } - else - return true; - } -} - -var blocksToMoveScanner = new DocumentScanner(new BlocksToMoveVisitor(), true); - -function movemouse(e) -{ - if (isdrag) - { - var currentMouseX = nn6 ? e.clientX : event.clientX; - var currentMouseY = nn6 ? e.clientY : event.clientY; - var newElementX = elementStartX + currentMouseX - mouseStartX; - var newElementY = elementStartY + currentMouseY - mouseStartY; - - // check bounds - // note: the "-1" and "+1" is to avoid borders overlap - if(newElementX < bounds[0]) - newElementX = bounds[0] + 1; - if(newElementX + elementToMove.offsetWidth > bounds[2]) - newElementX = bounds[2] - elementToMove.offsetWidth - 1; - if(newElementY < bounds[1]) - newElementY = bounds[1] + 1; - if(newElementY + elementToMove.offsetHeight > bounds[3]) - newElementY = bounds[3] - elementToMove.offsetHeight - 1; - - // move element - elementToMove.style.left = newElementX + 'px'; - elementToMove.style.top = newElementY + 'px'; - -// elementToMove.style.left = newElementX / elementToMove.parentNode.offsetWidth * 100 + '%'; -// elementToMove.style.top = newElementY / elementToMove.parentNode.offsetHeight * 100 + '%'; - - elementToMove.style.right = null; - elementToMove.style.bottom = null; - - var i; - for(i = 0; i < blocksToMove.length; i++) - { - blocksToMove[i].onMove(); - } - return false; - } -} - -/** - * finds the innermost draggable element starting from the one that generated the event "e" - * (i.e.: the html element under mouse pointer), then setup the document's onmousemove function to - * move the element around. - */ -function startDrag(e) -{ - var eventSource = nn6 ? e.target : event.srcElement; - if(eventSource.tagName == 'HTML') - return; - - while (eventSource != document.body && !hasClass(eventSource, "draggable")) - { - eventSource = nn6 ? eventSource.parentNode : eventSource.parentElement; - } - - // if a draggable element was found, calculate its actual position - if (hasClass(eventSource, "draggable")) - { - isdrag = true; - elementToMove = eventSource; - - // set absolute positioning on the element - elementToMove.style.position = "absolute"; - - // calculate start point - elementStartX = elementToMove.offsetLeft; - elementStartY = elementToMove.offsetTop; - - // calculate mouse start point - mouseStartX = nn6 ? e.clientX : event.clientX; - mouseStartY = nn6 ? e.clientY : event.clientY; - - // calculate bounds as left, top, width, height of the parent element - if(getStyle(elementToMove.parentNode, "position") == 'absolute') - { - bounds[0] = 0; - bounds[1] = 0; - } - else - { - bounds[0] = calculateOffsetLeft(elementToMove.parentNode); - bounds[1] = calculateOffsetTop(elementToMove.parentNode); - } - bounds[2] = bounds[0] + elementToMove.parentNode.offsetWidth; - bounds[3] = bounds[1] + elementToMove.parentNode.offsetHeight; - - // either find the block related to the dragging element to call its onMove method - blocksToMove = new Array(); - - blocksToMoveScanner.scan(eventSource); - document.onmousemove = movemouse; - - originalZIndex = getStyle(elementToMove, "z-index"); - elementToMove.style.zIndex = "3"; - - return false; - } -} - -function stopDrag(e) -{ - isdrag=false; - if(elementToMove) - elementToMove.style.zIndex=originalZIndex; - elementToMove = null; - document.onmousemove = null; -} - -document.onmousedown = startDrag; -document.onmouseup = stopDrag; - - - -/************* - * Constants * - *************/ -var LEFT = 1; -var RIGHT = 2; -var UP = 4; -var DOWN = 8; -var HORIZONTAL = LEFT + RIGHT; -var VERTICAL = UP + DOWN; -var AUTO = HORIZONTAL + VERTICAL; - -var START = 0; -var END = 1; -var SCROLLBARS_WIDTH = 18; - -/************** - * Inspectors * - **************/ - -var inspectors = new Array(); - -/** - * The canvas class. - * This class is built on a div html element. - */ -function Canvas(htmlElement) -{ - /* - * initialization - */ - this.id = htmlElement.id; - this.htmlElement = htmlElement; - this.blocks = new Array(); - this.connectors = new Array(); - this.offsetLeft = calculateOffsetLeft(this.htmlElement); - this.offsetTop = calculateOffsetTop(this.htmlElement); - - this.width; - this.height; - - // create the inner div element - this.innerDiv = document.createElement("div"); - - this.initCanvas = function() - { - // setup the inner div - var children = this.htmlElement.childNodes; - var i; - var el; - var n = children.length; - for(i = 0; i < n; i++) - { - el = children[0]; - this.htmlElement.removeChild(el); - this.innerDiv.appendChild(el); - if(el.style) - el.style.zIndex = "2"; - } - this.htmlElement.appendChild(this.innerDiv); - - this.htmlElement.style.overflow = "auto"; - this.htmlElement.style.position = "relative"; - this.innerDiv.id = this.id + "_innerDiv"; - this.innerDiv.style.border = "none"; - this.innerDiv.style.padding = "0px"; - this.innerDiv.style.margin = "0px"; - this.innerDiv.style.position = "absolute"; - this.innerDiv.style.top = "0px"; - this.innerDiv.style.left = "0px"; - this.width = 0; - this.height = 0; - this.offsetLeft = calculateOffsetLeft(this.innerDiv); - this.offsetTop = calculateOffsetTop(this.innerDiv); - - // inspect canvas children to identify first level blocks - new DocumentScanner(this, true).scan(this.htmlElement); - - // now this.width and this.height are populated with minimum values needed for the inner - // blocks to fit, add 2 to avoid border overlap; - this.height += 2; - this.width += 2; - - var visibleWidth = this.htmlElement.offsetWidth - 2; // - 2 is to avoid border overlap - var visibleHeight = this.htmlElement.offsetHeight - 2; // - 2 is to avoid border overlap - - // consider the scrollbars width calculating the inner div size - if(this.height > visibleHeight) - visibleWidth -= SCROLLBARS_WIDTH; - if(this.width > visibleWidth) - visibleHeight -= SCROLLBARS_WIDTH; - - this.height = Math.max(this.height, visibleHeight); - this.width = Math.max(this.width, visibleWidth); - - this.innerDiv.style.width = this.width + "px"; - this.innerDiv.style.height = this.height + "px"; - - // init connectors - for(i = 0; i < this.connectors.length; i++) - { - this.connectors[i].initConnector(); - } - } - - this.visit = function(element) - { - if(element == this.htmlElement) - return true; - - // check the element dimensions against the acutal size of the canvas - this.width = Math.max(this.width, calculateOffsetLeft(element) - this.offsetLeft + element.offsetWidth); - this.height = Math.max(this.height, calculateOffsetTop(element) - this.offsetTop + element.offsetHeight); - - if(isBlock(element)) - { - // block found initialize it - var newBlock = new Block(element, this); - newBlock.initBlock(); - this.blocks.push(newBlock); - return false; - } - else if(isConnector(element)) - { - // connector found, just create it, source or destination blocks may not - // have been initialized yet - var newConnector = new Connector(element, this); - this.connectors.push(newConnector); - return false; - } - else - { - // continue searching nested elements - return true; - } - } - - /* - * methods - */ - this.print = function() - { - var output = '<ul><legend>canvas: ' + this.id + '</legend>'; - var i; - for(i = 0; i < this.blocks.length; i++) - { - output += '<li>'; - output += this.blocks[i].print(); - output += '</li>'; - } - output += '</ul>'; - return output; - } - - /* - * This function searches for a nested block with a given id - */ - this.findBlock = function(blockId) - { - var result; - var i; - for(i = 0; i < this.blocks.length && !result; i++) - { - result = this.blocks[i].findBlock(blockId); - } - - return result; - } - - this.toString = function() - { - return 'canvas: ' + this.id; - } -} - -/* - * Block class - */ -function Block(htmlElement, canvas) -{ - /* - * initialization - */ - - this.canvas = canvas; - this.htmlElement = htmlElement; - this.id = htmlElement.id; - this.blocks = new Array(); - this.moveListeners = new Array(); - - if(this.id == 'description2_out1') - var merda = 0; - this.currentTop = calculateOffsetTop(this.htmlElement) - this.canvas.offsetTop; - this.currentLeft = calculateOffsetLeft(this.htmlElement) - this.canvas.offsetLeft; - - this.visit = function(element) - { - if(element == this.htmlElement) - { - // exclude itself - return true; - } - - if(isBlock(element)) - { - var innerBlock = new Block(element, this.canvas); - innerBlock.initBlock(); - this.blocks.push(innerBlock); - this.moveListeners.push(innerBlock); - return false; - } - else - return true; - } - - this.initBlock = function() - { - // inspect block children to identify nested blocks - - new DocumentScanner(this, true).scan(this.htmlElement); - } - - this.top = function() - { - return this.currentTop; - } - - this.left = function() - { - return this.currentLeft; - } - - this.width = function() - { - return this.htmlElement.offsetWidth; - } - - this.height = function() - { - return this.htmlElement.offsetHeight; - } - - /* - * methods - */ - this.print = function() - { - var output = 'block: ' + this.id; - if(this.blocks.length > 0) - { - output += '<ul>'; - var i; - for(i = 0; i < this.blocks.length; i++) - { - output += '<li>'; - output += this.blocks[i].print(); - output += '</li>'; - } - output += '</ul>'; - } - return output; - } - - /* - * This function searches for a nested block (or the block itself) with a given id - */ - this.findBlock = function(blockId) - { - if(this.id == blockId) - return this; - - var result; - var i; - for(i = 0; i < this.blocks.length && !result; i++) - { - result = this.blocks[i].findBlock(blockId); - } - - return result; - } - - this.move = function(left, top) - { - this.htmlElement.style.left = left; - this.htmlElement.style.top = top; - - this.onMove(); - } - - this.onMove = function() - { - var i; - this.currentLeft = calculateOffsetLeft(this.htmlElement) - this.canvas.offsetLeft; - this.currentTop = calculateOffsetTop(this.htmlElement) - this.canvas.offsetTop; - // notify listeners - for(i = 0; i < this.moveListeners.length; i++) - { - this.moveListeners[i].onMove(); - } - } - - this.toString = function() - { - return 'block: ' + this.id; - } -} - -/** - * This class represents a connector segment, it is drawn via a div element. - * A segment has a starting point defined by the properties startX and startY, a length, - * a thickness and an orientation. - * Allowed values for the orientation property are defined by the constants UP, LEFT, DOWN and RIGHT. - */ -function Segment(id, parentElement) -{ - this.id = id; - this.htmlElement = document.createElement('div'); - this.htmlElement.id = id; - this.htmlElement.style.position = 'absolute'; - this.htmlElement.style.overflow = 'hidden'; - parentElement.appendChild(this.htmlElement); - - this.startX; - this.startY; - this.length; - this.thickness; - this.orientation; - this.nextSegment; - this.visible = true; - - /** - * draw the segment. This operation is cascaded to next segment if any. - */ - this.draw = function() - { - // set properties to next segment - if(this.nextSegment) - { - this.nextSegment.startX = this.getEndX(); - this.nextSegment.startY = this.getEndY(); - } - - if(this.visible) - this.htmlElement.style.display = 'block'; - else - this.htmlElement.style.display = 'none'; - - switch(this.orientation) - { - case LEFT: - this.htmlElement.style.left = (this.startX - this.length) + "px"; - this.htmlElement.style.top = this.startY + "px"; - this.htmlElement.style.width = this.length + "px"; - this.htmlElement.style.height = this.thickness + "px"; - break; - case RIGHT: - this.htmlElement.style.left = this.startX + "px"; - this.htmlElement.style.top = this.startY + "px"; - if(this.nextSegment) - this.htmlElement.style.width = this.length + this.thickness + "px"; - else - this.htmlElement.style.width = this.length + "px"; - this.htmlElement.style.height = this.thickness + "px"; - break; - case UP: - this.htmlElement.style.left = this.startX + "px"; - this.htmlElement.style.top = (this.startY - this.length) + "px"; - this.htmlElement.style.width = this.thickness + "px"; - this.htmlElement.style.height = this.length + "px"; - break; - case DOWN: - this.htmlElement.style.left = this.startX + "px"; - this.htmlElement.style.top = this.startY + "px"; - this.htmlElement.style.width = this.thickness + "px"; - if(this.nextSegment) - this.htmlElement.style.height = this.length + this.thickness + "px"; - else - this.htmlElement.style.height = this.length + "px"; - break; - } - - if(this.nextSegment) - this.nextSegment.draw(); - } - - /** - * Returns the "left" coordinate of the end point of this segment - */ - this.getEndX = function() - { - switch(this.orientation) - { - case LEFT: return this.startX - this.length; - case RIGHT: return this.startX + this.length; - case DOWN: return this.startX; - case UP: return this.startX; - } - } - - /** - * Returns the "top" coordinate of the end point of this segment - */ - this.getEndY = function() - { - switch(this.orientation) - { - case LEFT: return this.startY; - case RIGHT: return this.startY; - case DOWN: return this.startY + this.length; - case UP: return this.startY - this.length; - } - } - - /** - * Append another segment to the end point of this. - * If another segment is already appended to this, cascades the operation so - * the given next segment will be appended to the tail of the segments chain. - */ - this.append = function(nextSegment) - { - if(!nextSegment) - return; - if(!this.nextSegment) - { - this.nextSegment = nextSegment; - this.nextSegment.startX = this.getEndX(); - this.nextSegment.startY = this.getEndY(); - } - else - this.nextSegment.append(nextSegment); - } - - this.detach = function() - { - var s = this.nextSegment; - this.nextSegment = null; - return s; - } - - /** - * hides this segment and all the following - */ - this.cascadeHide = function() - { - this.visible = false; - if(this.nextSegment) - this.nextSegment.cascadeHide(); - } -} -/** - * Connector class. - * The init function takes two Block objects as arguments representing - * the source and destination of the connector - */ -function Connector(htmlElement, canvas) -{ - /** - * declaring html element - */ - this.htmlElement = htmlElement; - - /** - * the canvas this connector is in - */ - this.canvas = canvas; - - /** - * the source block - */ - this.source = null; - - /** - * the destination block - */ - this.destination = null; - - /** - * preferred orientation - */ - this.preferredSourceOrientation = AUTO; - this.preferredDestinationOrientation = AUTO; - - /** - * css class to be applied to the connector's segments - */ - this.connectorClass; - - /** - * minimum length for a connector segment. - */ - this.minSegmentLength = 10; - - /** - * size of the connector, i.e.: thickness of the segments. - */ - this.size = 1; - - /** - * connector's color - */ - this.color = 'black'; - - /** - * move listeners, they are notify when connector moves - */ - this.moveListeners = new Array(); - - this.firstSegment; - - this.segmentsPool; - - this.segmentsNumber = 0; - - this.strategy; - - this.initConnector = function() - { - // detect the connector id - if(this.htmlElement.id) - this.id = this.htmlElement.id; - else - this.id = this.htmlElement.className; - - // split the class name to get the ids of the source and destination blocks - var splitted = htmlElement.className.split(' '); - if(splitted.length < 3) - { - alert('Unable to create connector \'' + id + '\', class is not in the correct format: connector <sourceBlockId>, <destBlockId>'); - return; - } - - this.connectorClass = splitted[0] + ' ' + splitted[1] + ' ' + splitted[2]; - - this.source = this.canvas.findBlock(splitted[1]); - if(!this.source) - { - alert('cannot find source block with id \'' + splitted[1] + '\''); - return; - } - - this.destination = this.canvas.findBlock(splitted[2]); - if(!this.destination) - { - alert('cannot find destination block with id \'' + splitted[2] + '\''); - return; - } - - // check preferred orientation - if(hasClass(this.htmlElement, 'vertical')) - { - this.preferredSourceOrientation = VERTICAL; - this.preferredDestinationOrientation = VERTICAL; - } - else if(hasClass(this.htmlElement, 'horizontal')) - { - this.preferredSourceOrientation = HORIZONTAL; - this.preferredDestinationOrientation = HORIZONTAL; - } - else - { - // check preferred orientation on source side - if(hasClass(this.htmlElement, 'vertical_start')) - this.preferredSourceOrientation = VERTICAL; - else if(hasClass(this.htmlElement, 'horizontal_start')) - this.preferredSourceOrientation = HORIZONTAL; - else if(hasClass(this.htmlElement, 'left_start')) - this.preferredSourceOrientation = LEFT; - else if(hasClass(this.htmlElement, 'right_start')) - this.preferredSourceOrientation = RIGHT; - else if(hasClass(this.htmlElement, 'up_start')) - this.preferredSourceOrientation = UP; - else if(hasClass(this.htmlElement, 'down_start')) - this.preferredSourceOrientation = DOWN; - - // check preferred orientation on destination side - if(hasClass(this.htmlElement, 'vertical_end')) - this.preferredDestinationOrientation = VERTICAL; - else if(hasClass(this.htmlElement, 'horizontal_end')) - this.preferredDestinationOrientation = HORIZONTAL; - else if(hasClass(this.htmlElement, 'left_end')) - this.preferredDestinationOrientation = LEFT; - else if(hasClass(this.htmlElement, 'right_end')) - this.preferredDestinationOrientation = RIGHT; - else if(hasClass(this.htmlElement, 'up_end')) - this.preferredDestinationOrientation = UP; - else if(hasClass(this.htmlElement, 'down_end')) - this.preferredDestinationOrientation = DOWN; - } - - // get the first strategy as default - this.strategy = strategies[0](this); - this.repaint(); - - this.source.moveListeners.push(this); - this.destination.moveListeners.push(this); - - // call inspectors for this connector - var i; - for(i = 0; i < inspectors.length; i++) - { - inspectors[i].inspect(this); - } - - // remove old html element - this.htmlElement.parentNode.removeChild(this.htmlElement); - } - - this.getStartSegment = function() - { - return this.firstSegment; - } - - this.getEndSegment = function() - { - var s = this.firstSegment; - while(s.nextSegment) - s = s.nextSegment; - return s; - } - - this.getMiddleSegment = function() - { - if(!this.strategy) - return null; - else - return this.strategy.getMiddleSegment(); - } - - this.createSegment = function() - { - var segment; - - // if the pool contains more objects, borrow the segment, create it otherwise - if(this.segmentsPool) - { - segment = this.segmentsPool; - this.segmentsPool = this.segmentsPool.detach(); - } - else - { - segment = new Segment(this.id + "_" + (this.segmentsNumber + 1), this.canvas.htmlElement); - segment.htmlElement.className = this.connectorClass; - if(!getStyle(segment.htmlElement, 'background-color')) - segment.htmlElement.style.backgroundColor = this.color; - segment.thickness = this.size; - } - this.segmentsNumber++; - - if(this.firstSegment) - this.firstSegment.append(segment); - else - this.firstSegment = segment; - segment.visible = true; - return segment; - } - - /** - * Repaints the connector - */ - this.repaint = function() - { - // check strategies fitness and choose the best fitting one - var i; - var maxFitness = 0; - var fitness; - var s; - - // check if any strategy is possible with preferredOrientation - for(i = 0; i < strategies.length; i++) - { - this.clearSegments(); - - fitness = 0; - s = strategies[i](this); - if(s.isApplicable()) - { - fitness++; - s.paint(); - // check resulting orientation against the preferred orientations - if((this.firstSegment.orientation & this.preferredSourceOrientation) != 0) - fitness++; - if((this.getEndSegment().orientation & this.preferredDestinationOrientation) != 0) - fitness++; - } - - if(fitness > maxFitness) - { - this.strategy = s; - maxFitness = fitness; - } - } - - this.clearSegments(); - - this.strategy.paint(); - this.firstSegment.draw(); - - // this is needed to actually hide unused html elements - if(this.segmentsPool) - this.segmentsPool.draw(); - } - - /** - * Hide all the segments and return them to pool - */ - this.clearSegments = function() - { - if(this.firstSegment) - { - this.firstSegment.cascadeHide(); - this.firstSegment.append(this.segmentsPool); - this.segmentsPool = this.firstSegment; - this.firstSegment = null; - } - } - - this.onMove = function() - { - this.repaint(); - - // notify listeners - var i; - for(i = 0; i < this.moveListeners.length; i++) - this.moveListeners[i].onMove(); - } -} - -var strategies = new Array(); - -function ConnectorEnd(htmlElement, connector, side) -{ - this.side = side; - this.htmlElement = htmlElement; - this.connector = connector; - connector.canvas.htmlElement.appendChild(htmlElement); - // strip extension - if(this.htmlElement.tagName.toLowerCase() == "img") - { - this.src = this.htmlElement.src.substring(0, this.htmlElement.src.lastIndexOf('.')); - this.srcExtension = this.htmlElement.src.substring(this.htmlElement.src.lastIndexOf('.')); - this.htmlElement.style.zIndex = getStyle(this.connector.htmlElement, "z-index"); - } - - this.orientation; - - this.repaint = function() - { - this.htmlElement.style.position = 'absolute'; - - var left; - var top; - var segment; - var orientation; - - if(this.side == START) - { - segment = connector.getStartSegment(); - left = segment.startX; - top = segment.startY; - orientation = segment.orientation; - // swap orientation - if((orientation & VERTICAL) != 0) - orientation = (~orientation) & VERTICAL; - else - orientation = (~orientation) & HORIZONTAL; - } - else - { - segment = connector.getEndSegment(); - left = segment.getEndX(); - top = segment.getEndY(); - orientation = segment.orientation; - } - - switch(orientation) - { - case LEFT: - top -= (this.htmlElement.offsetHeight - segment.thickness) / 2; - break; - case RIGHT: - left -= this.htmlElement.offsetWidth; - top -= (this.htmlElement.offsetHeight - segment.thickness) / 2; - break; - case DOWN: - top -= this.htmlElement.offsetHeight; - left -= (this.htmlElement.offsetWidth - segment.thickness) / 2; - break; - case UP: - left -= (this.htmlElement.offsetWidth - segment.thickness) / 2; - break; - } - - this.htmlElement.style.left = Math.ceil(left) + "px"; - this.htmlElement.style.top = Math.ceil(top) + "px"; - - if(this.htmlElement.tagName.toLowerCase() == "img" && this.orientation != orientation) - { - var orientationSuffix; - switch(orientation) - { - case UP: orientationSuffix = "u"; break; - case DOWN: orientationSuffix = "d"; break; - case LEFT: orientationSuffix = "l"; break; - case RIGHT: orientationSuffix = "r"; break; - } - this.htmlElement.src = this.src + "_" + orientationSuffix + this.srcExtension; - } - this.orientation = orientation; - } - - this.onMove = function() - { - this.repaint(); - } -} - -function SideConnectorLabel(connector, htmlElement, side) -{ - this.connector = connector; - this.htmlElement = htmlElement; - this.side = side; - this.connector.htmlElement.parentNode.appendChild(htmlElement); - - this.repaint = function() - { - this.htmlElement.style.position = 'absolute'; - var left; - var top; - var segment; - - if(this.side == START) - { - segment = this.connector.getStartSegment(); - left = segment.startX; - top = segment.startY; - if(segment.orientation == LEFT) - left -= this.htmlElement.offsetWidth; - if(segment.orientation == UP) - top -= this.htmlElement.offsetHeight; - - if((segment.orientation & HORIZONTAL) != 0 && top < this.connector.getEndSegment().getEndY()) - top -= this.htmlElement.offsetHeight; - if((segment.orientation & VERTICAL) != 0 && left < this.connector.getEndSegment().getEndX()) - left -= this.htmlElement.offsetWidth; - } - else - { - segment = this.connector.getEndSegment(); - left = segment.getEndX(); - top = segment.getEndY(); - if(segment.orientation == RIGHT) - left -= this.htmlElement.offsetWidth; - if(segment.orientation == DOWN) - top -= this.htmlElement.offsetHeight; - if((segment.orientation & HORIZONTAL) != 0 && top < this.connector.getStartSegment().startY) - top -= this.htmlElement.offsetHeight; - if((segment.orientation & VERTICAL) != 0 && left < this.connector.getStartSegment().startX) - left -= this.htmlElement.offsetWidth; - } - - this.htmlElement.style.left = Math.ceil(left) + "px"; - this.htmlElement.style.top = Math.ceil(top) + "px"; - } - - this.onMove = function() - { - this.repaint(); - } -} - -function MiddleConnectorLabel(connector, htmlElement) -{ - this.connector = connector; - this.htmlElement = htmlElement; - this.connector.canvas.htmlElement.appendChild(htmlElement); - - this.repaint = function() - { - this.htmlElement.style.position = 'absolute'; - - var left; - var top; - var segment = connector.getMiddleSegment(); - - if((segment.orientation & VERTICAL) != 0) - { - // put label at middle height on right side of the connector - top = segment.htmlElement.offsetTop + (segment.htmlElement.offsetHeight - this.htmlElement.offsetHeight) / 2; - left = segment.htmlElement.offsetLeft; - } - else - { - // put connector below the connector at middle widths - top = segment.htmlElement.offsetTop; - left = segment.htmlElement.offsetLeft + (segment.htmlElement.offsetWidth - this.htmlElement.offsetWidth) / 2;; - } - - this.htmlElement.style.left = Math.ceil(left) + "px"; - this.htmlElement.style.top = Math.ceil(top) + "px"; - } - - this.onMove = function() - { - this.repaint(); - } -} - -/* - * Inspector classes - */ - -function ConnectorEndsInspector() -{ - this.inspect = function(connector) - { - var children = connector.htmlElement.childNodes; - var i; - for(i = 0; i < children.length; i++) - { - if(hasClass(children[i], "connector-end")) - { - var newElement = new ConnectorEnd(children[i], connector, END); - newElement.repaint(); - connector.moveListeners.push(newElement); - } - else if(hasClass(children[i], "connector-start")) - { - var newElement = new ConnectorEnd(children[i], connector, START); - newElement.repaint(); - connector.moveListeners.push(newElement); - } - } - } -} - -function ConnectorLabelsInspector() -{ - this.inspect = function(connector) - { - var children = connector.htmlElement.childNodes; - var i; - for(i = 0; i < children.length; i++) - { - if(hasClass(children[i], "source-label")) - { - var newElement = new SideConnectorLabel(connector, children[i], START); - newElement.repaint(); - connector.moveListeners.push(newElement); - } - else if(hasClass(children[i], "middle-label")) - { - var newElement = new MiddleConnectorLabel(connector, children[i]); - newElement.repaint(); - connector.moveListeners.push(newElement); - } - else if(hasClass(children[i], "destination-label")) - { - var newElement = new SideConnectorLabel(connector, children[i], END); - newElement.repaint(); - connector.moveListeners.push(newElement); - } - } - } -} - -/* - * Inspector registration - */ - -inspectors.push(new ConnectorEndsInspector()); -inspectors.push(new ConnectorLabelsInspector()); - -/* - * an array containing all the canvases in document - */ -var canvases = new Array(); - -/* - * This function initializes the js_graph objects inspecting the html document - */ -function initPageObjects() -{ - if(isCanvas(document.body)) - { - var newCanvas = new Canvas(document.body); - newCanvas.initCanvas(); - canvases.push(newCanvas); - } - else - { - var divs = document.getElementsByTagName('div'); - var i; - for(i = 0; i < divs.length; i++) - { - if(isCanvas(divs[i]) && !findCanvas(divs[i].id)) - { - var newCanvas = new Canvas(divs[i]); - newCanvas.initCanvas(); - canvases.push(newCanvas); - } - } - } -} - - -/* - * Utility functions - */ - - -function findCanvas(canvasId) -{ - var i; - for(i = 0; i < canvases.length; i++) - if(canvases[i].id == canvasId) - return canvases[i]; - return null; -} - -function findBlock(blockId) -{ - var i; - for(i = 0; i < canvases.length; i++) - { - var block = canvases[i].findBlock(blockId); - if(block) - return block; - } - return null; -} - -/* - * This function determines whether a html element is to be considered a canvas - */ -function isBlock(htmlElement) -{ - return hasClass(htmlElement, 'block'); -} - -/* - * This function determines whether a html element is to be considered a block - */ -function isCanvas(htmlElement) -{ - return hasClass(htmlElement, 'canvas'); -} - -/* - * This function determines whether a html element is to be considered a connector - */ -function isConnector(htmlElement) -{ - return htmlElement.className && htmlElement.className.match(new RegExp('connector .*')); -} - -/* - * This function calculates the absolute 'top' value for a html node - */ -function calculateOffsetTop(obj) -{ - var curtop = 0; - if (obj.offsetParent) - { - curtop = obj.offsetTop - while (obj = obj.offsetParent) - curtop += obj.offsetTop - } - else if (obj.y) - curtop += obj.y; - return curtop; -} - -/* - * This function calculates the absolute 'left' value for a html node - */ -function calculateOffsetLeft(obj) -{ - var curleft = 0; - if (obj.offsetParent) - { - curleft = obj.offsetLeft - while (obj = obj.offsetParent) - { - curleft += obj.offsetLeft; - } - } - else if (obj.x) - curleft += obj.x; - return curleft; -} - -function parseBorder(obj, side) -{ - var sizeString = getStyle(obj, "border-" + side + "-width"); - if(sizeString && sizeString != "") - { - if(sizeString.substring(sizeString.length - 2) == "px") - return parseInt(sizeString.substring(0, sizeString.length - 2)); - } - return 0; -} - -function hasClass(element, className) -{ - if(!element || !element.className) - return false; - - var classes = element.className.split(' '); - var i; - for(i = 0; i < classes.length; i++) - if(classes[i] == className) - return true; - return false; -} - -/** - * This function retrieves the actual value of a style property even if it is set via css. - */ -function getStyle(node, styleProp) -{ - // if not an element - if( node.nodeType != 1) - return; - - var value; - if (node.currentStyle) - { - // ie case - styleProp = replaceDashWithCamelNotation(styleProp); - value = node.currentStyle[styleProp]; - } - else if (window.getComputedStyle) - { - // mozilla case - value = document.defaultView.getComputedStyle(node, null).getPropertyValue(styleProp); - } - - return value; -} - -function replaceDashWithCamelNotation(value) -{ - var pos = value.indexOf('-'); - while(pos > 0 && value.length > pos + 1) - { - value = value.substring(0, pos) + value.substring(pos + 1, pos + 2).toUpperCase() + value.substring(pos + 2); - pos = value.indexOf('-'); - } - return value; -} - - -/******************************* - * Connector paint strategies. * - *******************************/ - -/** - * Horizontal "S" routing strategy. - */ -function HorizontalSStrategy(connector) -{ - this.connector = connector; - - this.startSegment; - this.middleSegment; - this.endSegment; - - this.strategyName = "horizontal_s"; - - this.getMiddleSegment = function() - { - return this.middleSegment; - } - - this.isApplicable = function() - { - var sourceLeft = this.connector.source.left(); - var sourceWidth = this.connector.source.width(); - var destinationLeft = this.connector.destination.left(); - var destinationWidth = this.connector.destination.width(); - - return Math.abs(2 * destinationLeft + destinationWidth - (2 * sourceLeft + sourceWidth)) - (sourceWidth + destinationWidth) > 4 * this.connector.minSegmentLength; - } - - this.paint = function() - { - this.startSegment = connector.createSegment(); - this.middleSegment = connector.createSegment(); - this.endSegment = connector.createSegment(); - - var sourceLeft = this.connector.source.left(); - var sourceTop = this.connector.source.top(); - var sourceWidth = this.connector.source.width(); - var sourceHeight = this.connector.source.height(); - - var destinationLeft = this.connector.destination.left(); - var destinationTop = this.connector.destination.top(); - var destinationWidth = this.connector.destination.width(); - var destinationHeight = this.connector.destination.height(); - - var hLength; - - this.startSegment.startY = Math.floor(sourceTop + sourceHeight / 2); - - // deduce which face to use on source and destination blocks - if(sourceLeft + sourceWidth / 2 < destinationLeft + destinationWidth / 2) - { - // use left side of the source block and right side of the destination block - this.startSegment.startX = sourceLeft + sourceWidth; - hLength = destinationLeft - (sourceLeft + sourceWidth); - } - else - { - // use right side of the source block and left side of the destination block - this.startSegment.startX = sourceLeft; - hLength = destinationLeft + destinationWidth - sourceLeft; - } - - // first horizontal segment positioning - this.startSegment.length = Math.floor(Math.abs(hLength) / 2); - this.startSegment.orientation = hLength > 0 ? RIGHT : LEFT; - - // vertical segment positioning - var vLength = Math.floor(destinationTop + destinationHeight / 2 - (sourceTop + sourceHeight / 2)); - this.middleSegment.length = Math.abs(vLength); - if(vLength == 0) - this.middleSegment.visible = false; - this.middleSegment.orientation = vLength > 0 ? DOWN : UP; - - // second horizontal segment positioning - this.endSegment.length = Math.floor(Math.abs(hLength) / 2); - this.endSegment.orientation = hLength > 0 ? RIGHT : LEFT; - } -} - -/** - * Vertical "S" routing strategy. - */ -function VerticalSStrategy(connector) -{ - this.connector = connector; - - this.startSegment; - this.middleSegment; - this.endSegment; - - this.strategyName = "vertical_s"; - - this.getMiddleSegment = function() - { - return this.middleSegment; - } - - this.isApplicable = function() - { - var sourceTop = this.connector.source.top(); - var sourceHeight = this.connector.source.height(); - var destinationTop = this.connector.destination.top(); - var destinationHeight = this.connector.destination.height(); - return Math.abs(2 * destinationTop + destinationHeight - (2 * sourceTop + sourceHeight)) - (sourceHeight + destinationHeight) > 4 * this.connector.minSegmentLength; - } - - this.paint = function() - { - this.startSegment = connector.createSegment(); - this.middleSegment = connector.createSegment(); - this.endSegment = connector.createSegment(); - - var sourceLeft = this.connector.source.left(); - var sourceTop = this.connector.source.top(); - var sourceWidth = this.connector.source.width(); - var sourceHeight = this.connector.source.height(); - - var destinationLeft = this.connector.destination.left(); - var destinationTop = this.connector.destination.top(); - var destinationWidth = this.connector.destination.width(); - var destinationHeight = this.connector.destination.height(); - - var vLength; - - this.startSegment.startX = Math.floor(sourceLeft + sourceWidth / 2); - - // deduce which face to use on source and destination blocks - if(sourceTop + sourceHeight / 2 < destinationTop + destinationHeight / 2) - { - // use bottom side of the source block and top side of destination block - this.startSegment.startY = sourceTop + sourceHeight; - vLength = destinationTop - (sourceTop + sourceHeight); - } - else - { - // use top side of the source block and bottom side of the destination block - this.startSegment.startY = sourceTop; - vLength = destinationTop + destinationHeight - sourceTop; - } - - // first vertical segment positioning - this.startSegment.length = Math.floor(Math.abs(vLength) / 2); - this.startSegment.orientation = vLength > 0 ? DOWN : UP; - - // horizontal segment positioning - var hLength = Math.floor(destinationLeft + destinationWidth / 2 - (sourceLeft + sourceWidth / 2)); - this.middleSegment.length = Math.abs(hLength); - this.middleSegment.orientation = hLength > 0 ? RIGHT : LEFT; - - // second vertical segment positioning - this.endSegment.length = Math.floor(Math.abs(vLength) / 2); - this.endSegment.orientation = vLength > 0 ? DOWN : UP; - } -} - -/** - * A horizontal "L" connector routing strategy - */ -function HorizontalLStrategy(connector) -{ - this.connector = connector; - - this.destination; - - this.startSegment; - this.endSegment; - - this.strategyName = "horizontal_L"; - - this.isApplicable = function() - { - var destMiddle = Math.floor(this.connector.destination.left() + this.connector.destination.width() / 2); - var sl = this.connector.source.left(); - var sw = this.connector.source.width(); - var dt = this.connector.destination.top(); - var dh = this.connector.destination.height(); - var sourceMiddle = Math.floor(this.connector.source.top() + this.connector.source.height() / 2); - - if(destMiddle > sl && destMiddle < sl + sw) - return false; - if(sourceMiddle > dt && sourceMiddle < dt + dh) - return false; - return true; - } - - /** - * Chooses the longest segment as the "middle" segment. - */ - this.getMiddleSegment = function() - { - if(this.startSegment.length > this.endSegment.length) - return this.startSegment; - else - return this.endSegment; - } - - this.paint = function() - { - this.startSegment = this.connector.createSegment(); - this.endSegment = this.connector.createSegment(); - - var destMiddleX = Math.floor(this.connector.destination.left() + this.connector.destination.width() / 2); - var sl = this.connector.source.left(); - var sw = this.connector.source.width(); - var dt = this.connector.destination.top(); - var dh = this.connector.destination.height(); - - this.startSegment.startY = Math.floor(this.connector.source.top() + this.connector.source.height() / 2); - - // decide which side of the source block to connect to - if(Math.abs(destMiddleX - sl) < Math.abs(destMiddleX - (sl + sw))) - { - // use the left face - this.startSegment.orientation = (destMiddleX < sl) ? LEFT : RIGHT; - this.startSegment.startX = sl; - } - else - { - // use the right face - this.startSegment.orientation = (destMiddleX > (sl + sw)) ? RIGHT : LEFT; - this.startSegment.startX = sl + sw; - } - - this.startSegment.length = Math.abs(destMiddleX - this.startSegment.startX); - - // decide which side of the destination block to connect to - if(Math.abs(this.startSegment.startY - dt) < Math.abs(this.startSegment.startY - (dt + dh))) - { - // use the upper face - this.endSegment.orientation = (this.startSegment.startY < dt) ? DOWN : UP; - this.endSegment.length = Math.abs(this.startSegment.startY - dt); - } - else - { - // use the lower face - this.endSegment.orientation = (this.startSegment.startY > (dt + dh)) ? UP : DOWN; - this.endSegment.length = Math.abs(this.startSegment.startY - (dt + dh)); - } - } -} - -/** - * Vertical "L" connector routing strategy - */ -function VerticalLStrategy(connector) -{ - this.connector = connector; - - this.startSegment; - this.endSegment; - - this.strategyName = "vertical_L"; - - this.isApplicable = function() - { - var sourceMiddle = Math.floor(this.connector.source.left() + this.connector.source.width() / 2); - var dl = this.connector.destination.left(); - var dw = this.connector.destination.width(); - var st = this.connector.source.top(); - var sh = this.connector.source.height(); - var destMiddle = Math.floor(this.connector.destination.top() + this.connector.destination.height() / 2); - - if(sourceMiddle > dl && sourceMiddle < dl + dw) - return false; - if(destMiddle > st && destMiddle < st + sh) - return false; - return true; - } - - /** - * Chooses the longest segment as the "middle" segment. - */ - this.getMiddleSegment = function() - { - if(this.startSegment.length > this.endSegment.length) - return this.startSegment; - else - return this.endSegment; - } - - this.paint = function() - { - this.startSegment = this.connector.createSegment(); - this.endSegment = this.connector.createSegment(); - - var destMiddleY = Math.floor(this.connector.destination.top() + this.connector.destination.height() / 2); - var dl = this.connector.destination.left(); - var dw = this.connector.destination.width(); - var st = this.connector.source.top(); - var sh = this.connector.source.height(); - - this.startSegment.startX = Math.floor(this.connector.source.left() + this.connector.source.width() / 2); - - // decide which side of the source block to connect to - if(Math.abs(destMiddleY - st) < Math.abs(destMiddleY - (st + sh))) - { - // use the upper face - this.startSegment.orientation = (destMiddleY < st) ? UP : DOWN; - this.startSegment.startY = st; - } - else - { - // use the lower face - this.startSegment.orientation = (destMiddleY > (st + sh)) ? DOWN : UP; - this.startSegment.startY = st + sh; - } - - this.startSegment.length = Math.abs(destMiddleY - this.startSegment.startY); - - // decide which side of the destination block to connect to - if(Math.abs(this.startSegment.startX - dl) < Math.abs(this.startSegment.startX - (dl + dw))) - { - // use the left face - this.endSegment.orientation = (this.startSegment.startX < dl) ? RIGHT : LEFT; - this.endSegment.length = Math.abs(this.startSegment.startX - dl); - } - else - { - // use the right face - this.endSegment.orientation = (this.startSegment.startX > dl + dw) ? LEFT : RIGHT; - this.endSegment.length = Math.abs(this.startSegment.startX - (dl + dw)); - } - } -} - -function HorizontalCStrategy(connector, startOrientation) -{ - this.connector = connector; - - this.startSegment; - this.middleSegment; - this.endSegment; - - this.strategyName = "horizontal_c"; - - this.getMiddleSegment = function() - { - return this.middleSegment; - } - - this.isApplicable = function() - { - return true; - } - - this.paint = function() - { - this.startSegment = connector.createSegment(); - this.middleSegment = connector.createSegment(); - this.endSegment = connector.createSegment(); - - var sign = 1; - if(startOrientation == RIGHT) - sign = -1; - - var startX = this.connector.source.left(); - if(startOrientation == RIGHT) - startX += this.connector.source.width(); - var startY = Math.floor(this.connector.source.top() + this.connector.source.height() / 2); - - var endX = this.connector.destination.left(); - if(startOrientation == RIGHT) - endX += this.connector.destination.width(); - var endY = Math.floor(this.connector.destination.top() + this.connector.destination.height() / 2); - - this.startSegment.startX = startX; - this.startSegment.startY = startY; - this.startSegment.orientation = startOrientation; - this.startSegment.length = this.connector.minSegmentLength + Math.max(0, sign * (startX - endX)); - - var vLength = endY - startY; - this.middleSegment.orientation = vLength > 0 ? DOWN : UP; - this.middleSegment.length = Math.abs(vLength); - - this.endSegment.orientation = startOrientation == LEFT ? RIGHT : LEFT; - this.endSegment.length = Math.max(0, sign * (endX - startX)) + this.connector.minSegmentLength; - } -} - -function VerticalCStrategy(connector, startOrientation) -{ - this.connector = connector; - - this.startSegment; - this.middleSegment; - this.endSegment; - - this.strategyName = "vertical_c"; - - this.getMiddleSegment = function() - { - return this.middleSegment; - } - - this.isApplicable = function() - { - return true; - } - - this.paint = function() - { - this.startSegment = connector.createSegment(); - this.middleSegment = connector.createSegment(); - this.endSegment = connector.createSegment(); - - var sign = 1; - if(startOrientation == DOWN) - sign = -1; - - var startY = this.connector.source.top(); - if(startOrientation == DOWN) - startY += this.connector.source.height(); - var startX = Math.floor(this.connector.source.left() + this.connector.source.width() / 2); - - var endY = this.connector.destination.top(); - if(startOrientation == DOWN) - endY += this.connector.destination.height(); - var endX = Math.floor(this.connector.destination.left() + this.connector.destination.width() / 2); - - this.startSegment.startX = startX; - this.startSegment.startY = startY; - this.startSegment.orientation = startOrientation; - this.startSegment.length = this.connector.minSegmentLength + Math.max(0, sign * (startY - endY)); - - var hLength = endX - startX; - this.middleSegment.orientation = hLength > 0 ? RIGHT : LEFT; - this.middleSegment.length = Math.abs(hLength); - - this.endSegment.orientation = startOrientation == UP ? DOWN : UP; - this.endSegment.length = Math.max(0, sign * (endY - startY)) + this.connector.minSegmentLength; - } -} - - -strategies[0] = function(connector) {return new HorizontalSStrategy(connector)}; -strategies[1] = function(connector) {return new VerticalSStrategy(connector)}; -strategies[2] = function(connector) {return new HorizontalLStrategy(connector)}; -strategies[3] = function(connector) {return new VerticalLStrategy(connector)}; -strategies[4] = function(connector) {return new HorizontalCStrategy(connector, LEFT)}; -strategies[5] = function(connector) {return new HorizontalCStrategy(connector, RIGHT)}; -strategies[6] = function(connector) {return new VerticalCStrategy(connector, UP)}; -strategies[7] = function(connector) {return new VerticalCStrategy(connector, DOWN)}; \ No newline at end of file