Index: lams_build/3rdParty.userlibraries =================================================================== diff -u -r7c06d9adee78c99dd6151735c95016f1e0b0f360 -ref201100ffd7ebdfcb18c72528f062379e4c65d3 --- lams_build/3rdParty.userlibraries (.../3rdParty.userlibraries) (revision 7c06d9adee78c99dd6151735c95016f1e0b0f360) +++ lams_build/3rdParty.userlibraries (.../3rdParty.userlibraries) (revision ef201100ffd7ebdfcb18c72528f062379e4c65d3) @@ -6,6 +6,16 @@ + + + + + + + + + + @@ -83,6 +93,8 @@ + + Index: lams_build/build.xml =================================================================== diff -u -r7c06d9adee78c99dd6151735c95016f1e0b0f360 -ref201100ffd7ebdfcb18c72528f062379e4c65d3 --- lams_build/build.xml (.../build.xml) (revision 7c06d9adee78c99dd6151735c95016f1e0b0f360) +++ lams_build/build.xml (.../build.xml) (revision ef201100ffd7ebdfcb18c72528f062379e4c65d3) @@ -116,6 +116,7 @@ + @@ -133,7 +134,8 @@ - + + Index: lams_build/liblist.txt =================================================================== diff -u -r7c06d9adee78c99dd6151735c95016f1e0b0f360 -ref201100ffd7ebdfcb18c72528f062379e4c65d3 --- lams_build/liblist.txt (.../liblist.txt) (revision 7c06d9adee78c99dd6151735c95016f1e0b0f360) +++ lams_build/liblist.txt (.../liblist.txt) (revision ef201100ffd7ebdfcb18c72528f062379e4c65d3) @@ -13,6 +13,17 @@ saaj.jar wsdl4j-1.5.1.jar 1.5.1 IBM JWSDL +batik batik-anim.jar + batik-bridge.jar + batik-css.jar + batik-dom.jar + batik-ext.jar + batik-gvt.jar + batik-parser.jar + batik-svg-dom.jar + batik-util.jar + batik-xml.jar 1.7 Apache License 2.0 Apache toolkit for manipulating images in the Scalable Vector Graphics (SVG) format + cglib cglib-nodep-2.1_2.jar 2.1_2 Apache License 2.0 (old one - to be deleted soon!) cglib_jboss404GA.jar 4.0.4.GA Apache License 2.0 JBoss Inc. JBoss @@ -73,6 +84,9 @@ wddx wddx.jar 1.1 WDDX +xml-commons xml-apis.jar + xml-apis-ext.jar 1.3 Apache License 2.0 Apache Common code and guidelines for xml projects + xstream jmock-2004-03-19.jar joda-time-0.98.jar 0.98 Joda Software License 1.0 Joda.org Joda Time stax-1.1.1-dev.jar 1.1.1-dev StAX is the reference implementation of the St... Index: lams_common/src/java/org/lamsfoundation/lams/learningdesign/service/ExportToolContentService.java =================================================================== diff -u -ra8d07f4579fd17f88434619258434bb777d0cd83 -ref201100ffd7ebdfcb18c72528f062379e4c65d3 --- lams_common/src/java/org/lamsfoundation/lams/learningdesign/service/ExportToolContentService.java (.../ExportToolContentService.java) (revision a8d07f4579fd17f88434619258434bb777d0cd83) +++ lams_common/src/java/org/lamsfoundation/lams/learningdesign/service/ExportToolContentService.java (.../ExportToolContentService.java) (revision ef201100ffd7ebdfcb18c72528f062379e4c65d3) @@ -25,6 +25,7 @@ package org.lamsfoundation.lams.learningdesign.service; import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -64,6 +65,8 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; import org.apache.log4j.Logger; +import org.apache.xml.serialize.OutputFormat; +import org.apache.xml.serialize.XMLSerializer; import org.jdom.Attribute; import org.jdom.Document; import org.jdom.Element; @@ -138,6 +141,7 @@ import org.lamsfoundation.lams.util.FileUtilException; import org.lamsfoundation.lams.util.MessageService; import org.lamsfoundation.lams.util.VersionUtil; +import org.lamsfoundation.lams.util.svg.SVGGenerator; import org.lamsfoundation.lams.util.zipfile.ZipFileUtil; import org.lamsfoundation.lams.util.zipfile.ZipFileUtilException; import org.springframework.beans.BeansException; @@ -182,6 +186,8 @@ public static final String TOOL_FILE_NAME = "tool.xml"; public static final String TOOL_FAILED_FILE_NAME = "export_failed.xml"; + + public static final String SVG_IMAGE_FILE_NAME = "learning_design.svg"; private static final String ERROR_TOOL_NOT_FOUND = "error.import.matching.tool.not.found"; @@ -656,6 +662,21 @@ XStream designXml = new XStream(); designXml.toXML(ldDto, ldFile); ldFile.close(); + + //generate SVG image + if (format != ExportToolContentService.PACKAGE_FORMAT_IMS) { + String svgFileName = FileUtil.getFullPath(contentDir, ExportToolContentService.SVG_IMAGE_FILE_NAME); + Writer svgFile = new OutputStreamWriter(new FileOutputStream(svgFileName), "UTF-8"); + SVGGenerator svgGenerator = SVGGenerator.getInstance(); + svgGenerator.generateSvg(ldDto); + OutputFormat outputFormat = new OutputFormat(svgGenerator.getSVGDocument()); + outputFormat.setLineWidth(65); + outputFormat.setIndenting(true); + outputFormat.setIndent(2); + XMLSerializer serializer = new XMLSerializer(svgFile, outputFormat); + serializer.serialize(svgGenerator.getSVGDocument()); + svgFile.close(); + } log.debug("Learning design xml export success"); @@ -700,6 +721,9 @@ } catch (IOException e) { log.error("IOException:", e); throw new ExportToolContentException(e); + } catch (JDOMException e) { + log.error("JDOMException:", e); + throw new ExportToolContentException(e); } } Index: lams_common/src/java/org/lamsfoundation/lams/util/svg/ActivityTreeNode.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/util/svg/ActivityTreeNode.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/util/svg/ActivityTreeNode.java (revision ef201100ffd7ebdfcb18c72528f062379e4c65d3) @@ -0,0 +1,231 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + +/* $Id$ */ +package org.lamsfoundation.lams.util.svg; + + +import java.awt.Dimension; +import java.awt.Point; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +import javax.swing.tree.DefaultMutableTreeNode; + +import org.lamsfoundation.lams.learningdesign.Activity; +import org.lamsfoundation.lams.learningdesign.dto.AuthoringActivityDTO; + +/** + * @author Andrey Balan + */ +public class ActivityTreeNode extends DefaultMutableTreeNode { + + private static final long serialVersionUID = 1L; + + public ActivityTreeNode() { + super(); + } + + public ActivityTreeNode(AuthoringActivityDTO activity) { + super(activity); + } + + public AuthoringActivityDTO getActivity() { + return (AuthoringActivityDTO) userObject; + } + + public List getChildren() { + Enumeration enumeration = children(); + return Collections.list(enumeration); + } + + /** + * Can be invoked only after the tree has been built already. + * + * @return + */ + public AuthoringActivityDTO getParentActivity() { + if (parent != null) { + ActivityTreeNode parentNode = (ActivityTreeNode) parent; + return (AuthoringActivityDTO) parentNode.getUserObject(); + } + + return null; + } + + /** + * Checks whether activity is a children of an optional sequence activity + * + * @param node + * @return + */ + public boolean isOptionalSequenceActivityChild() { + boolean isOptionalSequenceActivityChild = false; + + AuthoringActivityDTO activity = (AuthoringActivityDTO) userObject; + if ((activity.getParentActivityID() != null)) { + DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) parent; + AuthoringActivityDTO parentActivity = (AuthoringActivityDTO) parentNode.getUserObject(); + if (parentActivity.getActivityTypeID().equals(Activity.SEQUENCE_ACTIVITY_TYPE)) { + isOptionalSequenceActivityChild = true; + } + } + + return isOptionalSequenceActivityChild; + } + + /** + * Checks whether activity is a children of an optional sequence activity + * + * @param node + * @return + */ + public String getActivityColor() { + String color = ""; + + switch (getActivity().getActivityCategoryID()) { + case Activity.CATEGORY_SYSTEM: + color = ";fill:#d0defd"; + break; + case Activity.CATEGORY_COLLABORATION: + color = ";fill:#fffccb"; + break; + case Activity.CATEGORY_ASSESSMENT: + color = ";fill:#ece9f7"; + break; + case Activity.CATEGORY_CONTENT: + color = ";fill:#fdf1d3"; + break; + case Activity.CATEGORY_SPLIT: + color = ";fill:#FFFFFF"; + break; + case Activity.CATEGORY_RESPONSE: + color = ";fill:#e9f9c0"; + break; + } + + return color; + } + + public Dimension getActivityDimension() { + AuthoringActivityDTO activity = (AuthoringActivityDTO) userObject; + int childrenSize = getChildCount(); + + // according to the type of activity, we need to set up difference width and height for the To and From + // activity to draw the transition accordingly. + int width; + int height; + if (activity.getActivityTypeID().equals(Activity.SYNCH_GATE_ACTIVITY_TYPE) + || activity.getActivityTypeID().equals(Activity.SCHEDULE_GATE_ACTIVITY_TYPE) + || activity.getActivityTypeID().equals(Activity.PERMISSION_GATE_ACTIVITY_TYPE) + || activity.getActivityTypeID().equals(Activity.CONDITION_GATE_ACTIVITY_TYPE)) { + // this is a gate activity + width = SVGConstants.GATE_WIDTH; + height = SVGConstants.GATE_HEIGHT; + + } else if (activity.getActivityTypeID().equals(Activity.PARALLEL_ACTIVITY_TYPE)) { + // This is a parallel activity + // Given that for now all parallel activities are just two activities, we can hard code the width and height + width = SVGConstants.PARALLEL_OR_OPTIONS_ACTIVITY_WIDTH; + height = SVGConstants.PARALLEL_ACTIVITY_HEIGHT; + + } else if (activity.getActivityTypeID().equals(Activity.CHOSEN_BRANCHING_ACTIVITY_TYPE) + || activity.getActivityTypeID().equals(Activity.GROUP_BRANCHING_ACTIVITY_TYPE) + || activity.getActivityTypeID().equals(Activity.TOOL_BRANCHING_ACTIVITY_TYPE)) { + // This is a branching activity + width = SVGConstants.BRANCHING_ACTIVITY_WIDTH; + height = SVGConstants.BRANCHING_ACTIVITY_HEIGHT; + + } else if (activity.getActivityTypeID().equals(Activity.OPTIONS_WITH_SEQUENCES_TYPE)) { + // This is an optional sequence + width = getOptionalSequenceActivityWidth(); + height = (57 * childrenSize) + 49; + + } else if (activity.getActivityTypeID().equals(Activity.SEQUENCE_ACTIVITY_TYPE)) { + // This is a sequence within an optional + ActivityTreeNode parentNode = (ActivityTreeNode) getParent(); + width = parentNode.getOptionalSequenceActivityWidth() -8; + height = SVGConstants.TOOL_HEIGHT + 3; + + } else if (activity.getActivityTypeID().equals(Activity.OPTIONS_ACTIVITY_TYPE)) { + // This is an optional activity + width = SVGConstants.PARALLEL_OR_OPTIONS_ACTIVITY_WIDTH; + height = SVGConstants.OPTIONS_ACTIVITY_HEIGHT_MULTIPLIER * childrenSize + + SVGConstants.OPTIONS_ACTIVITY_HEIGHT_ADD; + + } else if (activity.getActivityTypeID().equals(Activity.FLOATING_ACTIVITY_TYPE)) { + // This is a support activity + width = (SVGConstants.TOOL_WIDTH +7) * childrenSize + 11; + height = SVGConstants.OPTIONS_ACTIVITY_HEIGHT_MULTIPLIER + SVGConstants.OPTIONS_ACTIVITY_HEIGHT_ADD; + + } else { + // This is a tool activity + width = SVGConstants.TOOL_WIDTH; + height = SVGConstants.TOOL_HEIGHT; + + // if this activity is a children of a sequence activity, if it is, then we need to change its size + if (isOptionalSequenceActivityChild()) { + width = SVGConstants.TOOL_INSIDE_OPTIONAL_WIDTH; + height = 43; + } + } + + return new Dimension(width, height); + } + + /** + * Returns activity's left upper point + * + * @return + */ + public Point getActivityCoordinates() { + AuthoringActivityDTO activity = getActivity(); + + int x = (activity.getxCoord() == null) ? 0 : activity.getxCoord(); + int y = (activity.getyCoord() == null) ? 0 : activity.getyCoord(); + + return new Point(x, y); + } + + /** + * Returns OptionalSequenceActivityWidth + * + * @param node node containing optional sequence activity + * @return + */ + private int getOptionalSequenceActivityWidth() { + // now find out how many activities each of the subsequences have so we can calculate the width + int maxChildren = 0; + for (ActivityTreeNode childNode : getChildren()) { + int childrenSize = childNode.getChildCount(); + if (childrenSize > maxChildren) { + maxChildren = childrenSize; + } + } + maxChildren = (maxChildren < 2) ? 2 : maxChildren; + int width = SVGConstants.TOOL_INSIDE_OPTIONAL_WIDTH * maxChildren + 20; + + return width; + } + +} Index: lams_common/src/java/org/lamsfoundation/lams/util/svg/SVGConstants.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/util/svg/SVGConstants.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/util/svg/SVGConstants.java (revision ef201100ffd7ebdfcb18c72528f062379e4c65d3) @@ -0,0 +1,62 @@ +package org.lamsfoundation.lams.util.svg; + +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + +/* $Id$ */ + +public class SVGConstants { + + public static final String SVG_NAMESPACE = "http://www.w3.org/2000/svg";//SVGDOMImplementation.SVG_NAMESPACE_URI; + public static final String SVG_NAMESPACE_XLINK = "http://www.w3.org/1999/xlink"; + + // canvas dimensions + public static final int CANVAS_WIDTH = 1024; + public static final int CANVAS_HEIGHT = 768; + + // gate dimensions + public static final int GATE_WIDTH = 30; + public static final int GATE_HEIGHT = 30; + // These are the octogon proportions so according to one point we calculate the new dimensions/proportions + public static final double[][] GATE_PROPORTIONS = { { 13.6, 34.4 }, { 1, 34.4 }, { -9.4, 24 }, { -9.4, 11.4 }, { 1, 1 }, + { 13.6, 1 }, { 24, 11.4 }, { 24, 24 } }; + + // tool dimensions + public static final int TOOL_WIDTH = 125; + public static final int TOOL_HEIGHT = 50; + public static final int TOOL_INSIDE_OPTIONAL_WIDTH = 60; + + public static final int PARALLEL_ACTIVITY_HEIGHT = 172; + public static final int OPTIONS_ACTIVITY_HEIGHT_MULTIPLIER = 63; + public static final int OPTIONS_ACTIVITY_HEIGHT_ADD = 53; + public static final int PARALLEL_OR_OPTIONS_ACTIVITY_WIDTH = 141; + public static final int CONTAINER_HEADER_HEIGHT = 39; + + public static final int BRANCHING_ACTIVITY_HEIGHT = 100; + public static final int BRANCHING_ACTIVITY_WIDTH = 165; + + public static final double BRANCHING_ACTIVITY_POINT = 8.5; + //distance between points in branching activity + public static final int BRANCHING_STEP = 15; + + +} Index: lams_common/src/java/org/lamsfoundation/lams/util/svg/SVGGenerator.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/util/svg/SVGGenerator.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/util/svg/SVGGenerator.java (revision ef201100ffd7ebdfcb18c72528f062379e4c65d3) @@ -0,0 +1,707 @@ +package org.lamsfoundation.lams.util.svg; +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + +/* $Id$ */ + +import java.awt.geom.Point2D; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; + +import org.apache.batik.dom.svg.SVGDOMImplementation; +import org.apache.commons.lang.StringUtils; +import org.apache.xml.serialize.OutputFormat; +import org.apache.xml.serialize.XMLSerializer; +import org.jdom.JDOMException; +import org.lamsfoundation.lams.learningdesign.Activity; +import org.lamsfoundation.lams.learningdesign.dto.AuthoringActivityDTO; +import org.lamsfoundation.lams.learningdesign.dto.LearningDesignDTO; +import org.lamsfoundation.lams.learningdesign.dto.TransitionDTO; +import org.lamsfoundation.lams.util.FileUtil; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.svg.SVGDocument; + +/** + * Generates SVG document based on exported learning design's xml file. + * + * To be able to see resulted SVG image in swing component use the following lines. + JSVGCanvas canvas = new JSVGCanvas(); + JFrame f = new JFrame(); + f.getContentPane().add(canvas); + canvas.setSVGDocument(svgGenerator.getSVGDocument()); + f.pack(); + f.setSize(CANVAS_WIDTH, CANVAS_HEIGHT); + f.setVisible(true); + * + * @author Andrey Balan + */ +public class SVGGenerator extends SVGConstants{ + + private SVGDocument doc; + + private SVGGenerator(SVGDocument svgGDocument) { + doc = svgGDocument; + } + + /** + * Sets up Svg root and defs. + */ + public static SVGGenerator getInstance() { + + // Create an SVG document. + DOMImplementation impl = SVGDOMImplementation.getDOMImplementation(); + SVGDocument doc = (SVGDocument) impl.createDocument(SVG_NAMESPACE, "svg", null); + // Get the root element (the 'svg' element). + Element svgRoot = doc.getDocumentElement(); + // Set the width and height attributes on the root 'svg' element. + svgRoot.setAttributeNS(null, "width", Integer.toString(CANVAS_WIDTH)); + svgRoot.setAttributeNS(null, "height", Integer.toString(CANVAS_HEIGHT)); + svgRoot.setAttributeNS(null, "xmlns", SVG_NAMESPACE); + svgRoot.setAttributeNS(null, "xmlns:xlink", SVG_NAMESPACE_XLINK); + + //create arrow definition + Element defs = doc.createElementNS(SVG_NAMESPACE, "defs"); + svgRoot.appendChild(defs); + Element marker = doc.createElementNS(SVG_NAMESPACE, "marker"); + marker.setAttributeNS(null, "id", "Triangle"); + marker.setAttributeNS(null, "viewBox", "0 0 10 10"); + marker.setAttributeNS(null, "refX", "0"); + marker.setAttributeNS(null, "refY", "5"); + marker.setAttributeNS(null, "markerUnits", "strokeWidth"); + marker.setAttributeNS(null, "markerWidth", "6"); + marker.setAttributeNS(null, "markerHeight", "5"); + marker.setAttributeNS(null, "orient", "auto"); + defs.appendChild(marker); + Element path = doc.createElementNS(SVG_NAMESPACE, "path"); + path.setAttributeNS(null, "d", "M 0 0 L 10 5 L 0 10 z"); + marker.appendChild(path); + + return new SVGGenerator(doc); + } + + public SVGDocument getSVGDocument() { + return doc; + } + + public void generateSvg(LearningDesignDTO learningDesign) throws JDOMException, IOException { + + //initialize all tree nodes + ArrayList activities = learningDesign.getActivities(); + HashMap allNodes = new HashMap(); + for (AuthoringActivityDTO activity : activities) { + ActivityTreeNode node = new ActivityTreeNode(activity); + allNodes.put(activity.getActivityID(), node); + } + + //construct activities tree + ActivityTreeNode root = new ActivityTreeNode(); + for (ActivityTreeNode node : allNodes.values()) { + AuthoringActivityDTO activity = node.getActivity(); + if (activity.getParentActivityID() == null) { + root.add(node); + } else { + Long parentId = activity.getParentActivityID(); + ActivityTreeNode parent = allNodes.get(parentId); + parent.add(node); + } + } + + //**************** Draw transitions******************************************************** + Element svgRoot = doc.getDocumentElement(); + ArrayList transitions = learningDesign.getTransitions(); + for (TransitionDTO transition : transitions) { + + ActivityTreeNode fromActivity = allNodes.get(transition.getFromActivityID()); + ActivityTreeNode toActivity = allNodes.get(transition.getToActivityID()); + Point2D fromIntersection = SVGTrigonometryUtils.getRectangleAndLineSegmentIntersection(fromActivity, toActivity); + Point2D toIntersection = SVGTrigonometryUtils.getRectangleAndLineSegmentIntersection(toActivity, fromActivity); + + //skip optional sequence's childs + if (fromActivity.isOptionalSequenceActivityChild()) { + continue; + } + + // Create the line + Element line = doc.createElementNS(SVG_NAMESPACE, "line"); + line.setAttributeNS(null, "id", transition.getFromActivityID() + "_to_" + transition.getToActivityID()); + line.setAttributeNS(null, "x1", Double.toString(fromIntersection.getX())); + line.setAttributeNS(null, "y1", Double.toString(fromIntersection.getY())); + line.setAttributeNS(null, "x2", Double.toString(toIntersection.getX())); + line.setAttributeNS(null, "y2", Double.toString(toIntersection.getY())); + line.setAttributeNS(null, "style", "stroke:#8C8FA6;stroke-width:2;opacity:1"); + line.setAttributeNS(null, "parentID", "0"); + + double a = (toIntersection.getX() - fromIntersection.getX()); + double b = (toIntersection.getY() - fromIntersection.getY()); + double yArrowShift = 5* b/Math.sqrt(a*a + b*b); + double xArrowShift = 5* a/Math.sqrt(a*a + b*b); + // Create the arrowhead + Element arrowhead = doc.createElementNS(SVG_NAMESPACE, "line"); + arrowhead.setAttributeNS(null, "id", "arrowhead_" + transition.getFromActivityID() + "_to_" + transition.getToActivityID()); + arrowhead.setAttributeNS(null, "x1", Double.toString(fromIntersection.getX())); + arrowhead.setAttributeNS(null, "y1", Double.toString(fromIntersection.getY())); + arrowhead.setAttributeNS(null, "x2", Double.toString((fromIntersection.getX() + toIntersection.getX())/2 - xArrowShift)); + arrowhead.setAttributeNS(null, "y2", Double.toString((fromIntersection.getY() + toIntersection.getY())/2 - yArrowShift)); + arrowhead.setAttributeNS(null, "style", "fill:#8C8FA6;stroke:#8C8FA6;stroke-width:2;opacity:1"); + arrowhead.setAttributeNS(null, "marker-end", "url(#Triangle)"); + arrowhead.setAttributeNS(null, "parentID", "0"); + + // Attach the line to the root 'svg' element. + svgRoot.appendChild(line); + svgRoot.appendChild(arrowhead); + } + + //**************** Draw activities ******************************************************** + //tree traverse + treeTraverse(root); + } + + /** + * Recursive tree traverse. + * + * @param doc + * @param svgRoot + * @param learningDesign + * @param node + */ + private void treeTraverse(ActivityTreeNode node) { + AuthoringActivityDTO activity = node.getActivity(); + + //draw root's activity, unless it's the start root which doesn't contain activity + if (activity != null) { + createActivity(node); + + //in case of branching activity don't traverse child activities + if (activity.getActivityTypeID().equals(Activity.CHOSEN_BRANCHING_ACTIVITY_TYPE) + || activity.getActivityTypeID().equals(Activity.GROUP_BRANCHING_ACTIVITY_TYPE) + || activity.getActivityTypeID().equals(Activity.TOOL_BRANCHING_ACTIVITY_TYPE)) { + return; + } + } + + //traverse child subtrees + for (ActivityTreeNode child : node.getChildren()) { + treeTraverse(child); + } + } + + /** + * Adds activity to SVG DOM. + * + * @param doc + * @param svgRoot + * @param learningDesign + * @param activity + */ + private void createActivity(ActivityTreeNode node) { + + AuthoringActivityDTO activity = node.getActivity(); + + // Create current activity element + Element g = doc.createElementNS(SVG_NAMESPACE, "g"); + String activityId = activity.getActivityID().toString(); + g.setAttributeNS(null, "id", activityId); + String parentID = (activity.getParentActivityID() == null) ? "0" : activity.getParentActivityID().toString(); + g.setAttributeNS(null, "parentID", parentID); + // Attach the g element to the root 'svg' element. + Element svgRoot = doc.getDocumentElement(); + svgRoot.appendChild(g); + + int x = node.getActivityCoordinates().x; + int y = node.getActivityCoordinates().y; + // activities with parents (paralles, optionals, branching, etc) + if (activity.getParentActivityID() != null) { + AuthoringActivityDTO parentActivity = node.getParentActivity(); + x += (parentActivity.getxCoord() == null) ? 0 : parentActivity.getxCoord(); + y += (parentActivity.getyCoord() == null) ? 0 : parentActivity.getyCoord(); + } + + Integer width = node.getActivityDimension().width; + Integer height = node.getActivityDimension().height; + String text = activity.getActivityTitle(); + + // if this is a stop gate we need to draw an octogon instead + if (activity.getActivityTypeID().equals(Activity.SYNCH_GATE_ACTIVITY_TYPE) + || activity.getActivityTypeID().equals(Activity.SCHEDULE_GATE_ACTIVITY_TYPE) + || activity.getActivityTypeID().equals(Activity.PERMISSION_GATE_ACTIVITY_TYPE) + || activity.getActivityTypeID().equals(Activity.CONDITION_GATE_ACTIVITY_TYPE)) { + // don't care about SYSTEM_GATE_ACTIVITY_TYPE + x += 8; + y -= 2; + + String finalProportions = ""; + for (double[] proportion : GATE_PROPORTIONS) { + finalProportions += (x + proportion[0]) + "," + (y + proportion[1]) + " "; + } + + Element polygon = doc.createElementNS(SVG_NAMESPACE, "polygon"); + polygon.setAttributeNS(null, "style", "fill:red;stroke:#000000;stroke-width:0.5px"); + polygon.setAttributeNS(null, "points", finalProportions); + g.appendChild(polygon); + + // calculate midpoint for STOP text + double x1 = x + GATE_PROPORTIONS[6][0]; + double x2 = x + GATE_PROPORTIONS[2][0]; + double midpointX = (x1 + x2) / 2; + + double y1 = y + GATE_PROPORTIONS[6][1]; + double y2 = y + GATE_PROPORTIONS[2][1]; + double midpointY = (y1 + y2) / 2 + 3; + + createText("Gate_" + activityId, midpointX, midpointY, "0", "middle", "10", "Verdana", "fill:#FFFFFF;stroke:#FFFFFF;stroke-width:.5;", "STOP", g); + + } else if (activity.getActivityTypeID().equals(Activity.PARALLEL_ACTIVITY_TYPE)) { + // This is a parallel activity + + String style = "stroke:black;stroke-width:1;opacity:1;fill:#d0defd"; + + // if the parallel is grouped, show it + if (activity.getApplyGrouping()) { + createGroupingEffect("grouping-" + activityId, x, y, width, height, style, g); + } + + //TODO may be switch to using the following operators... createRectangle(null, x, y, width, height, style, g); + Element parallelContainer = doc.createElementNS(SVG_NAMESPACE, "rect"); + parallelContainer.setAttributeNS(null, "x", Integer.toString(x)); + parallelContainer.setAttributeNS(null, "y", Integer.toString(y)); + parallelContainer.setAttributeNS(null, "width", Integer.toString(width)); + parallelContainer.setAttributeNS(null, "height", Integer.toString(height)); + parallelContainer.setAttributeNS(null, "style", style); + g.appendChild(parallelContainer); + + Element parallelHeader = doc.createElementNS(SVG_NAMESPACE, "rect"); + parallelHeader.setAttributeNS(null, "x", Integer.toString(x +4)); + parallelHeader.setAttributeNS(null, "y", Integer.toString(y +5)); + parallelHeader.setAttributeNS(null, "width", Integer.toString(width -8)); + parallelHeader.setAttributeNS(null, "height", Integer.toString(23)); + parallelHeader.setAttributeNS(null, "style", "fill:#A9C8FD;stroke:#E1F0FD;stroke-width:2.2;opacity:1"); + g.appendChild(parallelHeader); + + if (StringUtils.isNotEmpty(text)) { + createText("TextElement-" + activityId, x +9, y +19, null, "start", "12", "Arial", "fill:#828990", text, g); + } + + } else if (activity.getActivityTypeID().equals(Activity.CHOSEN_BRANCHING_ACTIVITY_TYPE) + || activity.getActivityTypeID().equals(Activity.GROUP_BRANCHING_ACTIVITY_TYPE) + || activity.getActivityTypeID().equals(Activity.TOOL_BRANCHING_ACTIVITY_TYPE)) { + // This is a branching activity + + // Given that for now all parallel activities are just two activities, we can hard code the width and height + String style = "stroke:black;stroke-width:1;opacity:1;fill:#d0defd"; + + // if the parallel is grouped, show it + if (activity.getApplyGrouping()) { + createGroupingEffect("grouping-" + activityId, x, y, width, height, style, g); + } + + Element branchingContainer = doc.createElementNS(SVG_NAMESPACE, "rect"); + branchingContainer.setAttributeNS(null, "x", Integer.toString(x)); + branchingContainer.setAttributeNS(null, "y", Integer.toString(y)); + branchingContainer.setAttributeNS(null, "width", Integer.toString(width)); + branchingContainer.setAttributeNS(null, "height", Integer.toString(height)); + branchingContainer.setAttributeNS(null, "style", style); + g.appendChild(branchingContainer); + + int startingPointX = x + 26; + int startingPointY = y + 40; + if (node.getChildCount() == 4) { + startingPointY -= 12; + } else if (node.getChildCount() > 4) { + startingPointY -= 2*12; + } + + Element startingPoint = doc.createElementNS(SVG_NAMESPACE, "rect"); + startingPoint.setAttributeNS(null, "x", Integer.toString(startingPointX)); + startingPoint.setAttributeNS(null, "y", Integer.toString(startingPointY)); + startingPoint.setAttributeNS(null, "width", Double.toString(BRANCHING_ACTIVITY_POINT + 0.5)); + startingPoint.setAttributeNS(null, "height", Double.toString(BRANCHING_ACTIVITY_POINT + 0.5)); + startingPoint.setAttributeNS(null, "style", "fill:#000000"); + g.appendChild(startingPoint); + + Iterator sequenceNodeIterator = node.getChildren().iterator(); + for (int sequenceIndex = -1; sequenceNodeIterator.hasNext() && (sequenceIndex < 4); sequenceIndex++) { + ActivityTreeNode sequenceNode = sequenceNodeIterator.next(); + double previousActivityPointX = startingPointX + BRANCHING_ACTIVITY_POINT/2; + double previousActivityPointY = startingPointY + BRANCHING_ACTIVITY_POINT/2; + + // Create the lines + Iterator activityNodeIterator = sequenceNode.getChildren().iterator(); + for (int activityIndex=1; activityNodeIterator.hasNext() && (activityIndex <= 6); activityIndex++, activityNodeIterator.next()) { + double activityPointX = startingPointX + activityIndex*BRANCHING_STEP + BRANCHING_ACTIVITY_POINT/2; + double activityPointY = startingPointY + sequenceIndex*BRANCHING_STEP + BRANCHING_ACTIVITY_POINT/2; + + Element line = doc.createElementNS(SVG_NAMESPACE, "line"); + line.setAttributeNS(null, "x1", Double.toString(previousActivityPointX)); + line.setAttributeNS(null, "y1", Double.toString(previousActivityPointY)); + line.setAttributeNS(null, "x2", Double.toString(activityPointX)); + line.setAttributeNS(null, "y2", Double.toString(activityPointY)); + line.setAttributeNS(null, "style", "stroke:black;stroke-width:1;"); + g.appendChild(line); + + previousActivityPointX = activityPointX; + previousActivityPointY = activityPointY; + } + + //check if we need to draw line connecting last activity with endingPoint + if (!sequenceNode.getActivity().getStopAfterActivity()) { + Element line = doc.createElementNS(SVG_NAMESPACE, "line"); + line.setAttributeNS(null, "x1", Double.toString(previousActivityPointX)); + line.setAttributeNS(null, "y1", Double.toString(previousActivityPointY)); + line.setAttributeNS(null, "x2", Double.toString(x + 132 + BRANCHING_ACTIVITY_POINT / 2)); + line.setAttributeNS(null, "y2", Double.toString(startingPointY + BRANCHING_ACTIVITY_POINT / 2)); + line.setAttributeNS(null, "style", "stroke:black;stroke-width:1;"); + g.appendChild(line); + + } + + // Create activity points + activityNodeIterator = sequenceNode.getChildren().iterator(); + for (int activityIndex=1; activityNodeIterator.hasNext() && (activityIndex <= 6); activityIndex++) { + ActivityTreeNode activityNode = activityNodeIterator.next(); + String activityStyle = sequenceNode.getActivity().getStopAfterActivity()&&!activityNodeIterator.hasNext() ? "stroke:red" : "stroke:black"; + activityStyle += ";stroke-width:0.8;opacity:1" + activityNode.getActivityColor(); + double activityPointX = startingPointX + activityIndex*BRANCHING_STEP; + double activityPointY = startingPointY + sequenceIndex*BRANCHING_STEP; + + Element activityPoint = doc.createElementNS(SVG_NAMESPACE, "rect"); + activityPoint.setAttributeNS(null, "x", Double.toString(activityPointX)); + activityPoint.setAttributeNS(null, "y", Double.toString(activityPointY)); + activityPoint.setAttributeNS(null, "width", "" + BRANCHING_ACTIVITY_POINT); + activityPoint.setAttributeNS(null, "height", "" + BRANCHING_ACTIVITY_POINT); + activityPoint.setAttributeNS(null, "style", activityStyle); + g.appendChild(activityPoint); + } + } + + Element endingPoint = doc.createElementNS(SVG_NAMESPACE, "rect"); + endingPoint.setAttributeNS(null, "x", Integer.toString(x + 132)); + endingPoint.setAttributeNS(null, "y", Integer.toString(startingPointY)); + endingPoint.setAttributeNS(null, "width", Double.toString(BRANCHING_ACTIVITY_POINT + 0.5)); + endingPoint.setAttributeNS(null, "height", Double.toString(BRANCHING_ACTIVITY_POINT + 0.5)); + endingPoint.setAttributeNS(null, "style", "fill:#000000"); + g.appendChild(endingPoint); + + if (StringUtils.isNotEmpty(text)) { + createText("TextElement-" + activityId, x + BRANCHING_ACTIVITY_WIDTH/2, y +90, null, "middle", "11.4", "Verdana", null, text, g); + } + + } else if (activity.getActivityTypeID().equals(Activity.OPTIONS_WITH_SEQUENCES_TYPE)) { + // This is an optional sequence + + Element optionalContainer = doc.createElementNS(SVG_NAMESPACE, "rect"); + optionalContainer.setAttributeNS(null, "x", Integer.toString(x)); + optionalContainer.setAttributeNS(null, "y", Integer.toString(y)); + optionalContainer.setAttributeNS(null, "width", Integer.toString(width)); + optionalContainer.setAttributeNS(null, "height", Integer.toString(height)); + optionalContainer.setAttributeNS(null, "style", "opacity:1;fill:#d0defd"); + g.appendChild(optionalContainer); + + Element optionalHeader = doc.createElementNS(SVG_NAMESPACE, "rect"); + optionalHeader.setAttributeNS(null, "x", Integer.toString(x +4)); + optionalHeader.setAttributeNS(null, "y", Integer.toString(y +5)); + optionalHeader.setAttributeNS(null, "width", Integer.toString(width -8)); + optionalHeader.setAttributeNS(null, "height", Integer.toString(CONTAINER_HEADER_HEIGHT)); + optionalHeader.setAttributeNS(null, "style", "fill:#A9C8FD;stroke:#E1F0FD;stroke-width:2.2;opacity:1"); + g.appendChild(optionalHeader); + + if (StringUtils.isNotEmpty(text)) { + createText("TextElement-" + activityId, x +9, y +19, null, "start", "12", "Arial", "fill:#828990", text, g); + } + + int optionalSequencesSize = node.getChildCount(); + createText("Children-" + activityId, x +9, y +19*2+1, null, "start", "11", "Arial", "fill:#828990", optionalSequencesSize + " - Sequences", g); + + } else if (activity.getActivityTypeID().equals(Activity.SEQUENCE_ACTIVITY_TYPE)) { + // This is a sequence within an optional + + ActivityTreeNode parentNode = (ActivityTreeNode) node.getParent(); + int indexInSiblings = parentNode.getIndex(node) % 6; + String color; + switch (indexInSiblings) { + case 1: + color = "BCD0FF"; + break; + case 2: + color = "C7F9AE"; + break; + case 3: + color = "FFEDC3"; + break; + case 4: + color = "EDDDF9"; + break; + case 5: + color = "E9E9E9"; + break; + default: + color = "FFFFB3"; + } + + Element sequenceContainer = doc.createElementNS(SVG_NAMESPACE, "rect"); + sequenceContainer.setAttributeNS(null, "x", Integer.toString(x)); + sequenceContainer.setAttributeNS(null, "y", Integer.toString(y)); + sequenceContainer.setAttributeNS(null, "width", Integer.toString(width)); + sequenceContainer.setAttributeNS(null, "height", Integer.toString(height)); + sequenceContainer.setAttributeNS(null, "style", "stroke:#E1F0FD;stroke-width:.4;opacity:1;fill:#" + color); + g.appendChild(sequenceContainer); + + } else if (activity.getActivityTypeID().equals(Activity.OPTIONS_ACTIVITY_TYPE)) { + // This is an optional activity + + int childActivitiesSize = node.getChildCount(); + + // Create rect + Element optionalContainer = doc.createElementNS(SVG_NAMESPACE, "rect"); + optionalContainer.setAttributeNS(null, "x", Integer.toString(x)); + optionalContainer.setAttributeNS(null, "y", Integer.toString(y)); + optionalContainer.setAttributeNS(null, "width", Integer.toString(width)); + optionalContainer.setAttributeNS(null, "height", Integer.toString(height)); + optionalContainer.setAttributeNS(null, "style", "opacity:1;fill:#d0defd"); + g.appendChild(optionalContainer); + + Element optionalHeader = doc.createElementNS(SVG_NAMESPACE, "rect"); + optionalHeader.setAttributeNS(null, "x", Integer.toString(x + 4)); + optionalHeader.setAttributeNS(null, "y", Integer.toString(y + 5)); + optionalHeader.setAttributeNS(null, "width", Integer.toString(width - 8)); + optionalHeader.setAttributeNS(null, "height", Integer.toString(CONTAINER_HEADER_HEIGHT)); + optionalHeader.setAttributeNS(null, "style", "fill:#A9C8FD;stroke:#E1F0FD;stroke-width:2.2;opacity:1"); + g.appendChild(optionalHeader); + + if (StringUtils.isNotEmpty(text)) { + createText("TextElement-" + activityId, x +9, y +19, null, "start", "12", "Arial", "fill:#828990", text, g); + } + + createText("Children-" + activityId, x +9, y +19*2+1, null, "start", "11", "Arial", "fill:#828990", childActivitiesSize + " - Activities", g); + + } else if (activity.getActivityTypeID().equals(Activity.FLOATING_ACTIVITY_TYPE)) { + // This is a support activity + + Element supportContainer = doc.createElementNS(SVG_NAMESPACE, "rect"); + supportContainer.setAttributeNS(null, "x", Integer.toString(x)); + supportContainer.setAttributeNS(null, "y", Integer.toString(y)); + supportContainer.setAttributeNS(null, "width", Integer.toString(width)); + supportContainer.setAttributeNS(null, "height", Integer.toString(height)); + supportContainer.setAttributeNS(null, "style", "opacity:1;fill:#d0defd"); + g.appendChild(supportContainer); + + Element supportHeader = doc.createElementNS(SVG_NAMESPACE, "rect"); + supportHeader.setAttributeNS(null, "x", Integer.toString(x +4)); + supportHeader.setAttributeNS(null, "y", Integer.toString(y +5)); + supportHeader.setAttributeNS(null, "width", Integer.toString(width -8)); + supportHeader.setAttributeNS(null, "height", Integer.toString(CONTAINER_HEADER_HEIGHT)); + supportHeader.setAttributeNS(null, "style", "fill:#A9C8FD;stroke:#E1F0FD;stroke-width:2.2;opacity:1"); + g.appendChild(supportHeader); + + if (StringUtils.isNotEmpty(text)) { + createText("TextElement-" + activityId, x +9, y +19, null, "start", "12", "Arial", "fill:#828990", text, g); + } + + int supportActivityChildrenSize = node.getChildCount(); + createText("Children-" + activityId, x +9, y +19*2+1, null, "start", "11", "Arial", "fill:#828990", supportActivityChildrenSize + " - Activities", g); + + } else { + // This is a tool activity + + // if this activity is a children of a sequence activity, if it is, then we need to change its size + if (node.isOptionalSequenceActivityChild()) { + ActivityTreeNode parentNode = (ActivityTreeNode) node.getParent(); + AuthoringActivityDTO grandParentActivity = parentNode.getParentActivity(); + x += (grandParentActivity.getxCoord() == null) ? 0 : grandParentActivity.getxCoord(); + y += (grandParentActivity.getyCoord() == null) ? 0 : grandParentActivity.getyCoord(); + text = null; + } + + String style = "stroke:black;stroke-width:0.8;opacity:1" + node.getActivityColor(); + + // if activity uses a grouping we need to add a second rect layer to show that it's grouped + if (activity.getApplyGrouping()) { + createGroupingEffect("grouping-" + activityId, x, y, width, height, style, g); + } + + // Create rect + Element activityRectangle = doc.createElementNS(SVG_NAMESPACE, "rect"); + activityRectangle.setAttributeNS(null, "id", "act" + activityId); + activityRectangle.setAttributeNS(null, "x", Integer.toString(x)); + activityRectangle.setAttributeNS(null, "y", Integer.toString(y)); + activityRectangle.setAttributeNS(null, "width", width.toString()); + activityRectangle.setAttributeNS(null, "height", height.toString()); + activityRectangle.setAttributeNS(null, "style", style); + g.appendChild(activityRectangle); + + // Create text label + if (text != null) { + int xText = x + (width / 2); + int yText = y + (height / 2) + 18; + createText("TextElement-" + activityId, xText, yText, null, "middle", "11.4", "Verdana", null, text, g); + } + + // Create image + int imageX = x + (width / 2) - 15; + int imageY = y + (height / 2) - 22; + if (node.isOptionalSequenceActivityChild()) { + imageX += 2; + imageY += 7; + } + String imagePath = activity.getLibraryActivityUIImage(); + // if png_filename is empty then this is a grouping act: + String imageFileName; + if (StringUtils.isBlank(imagePath)) { + imageFileName = "icon_grouping.png"; + } else { + imageFileName = FileUtil.getFileName(imagePath); + imageFileName = imageFileName.replaceFirst(".swf$", ".png"); + } + imageFileName = "http://lamscommunity.org/lamscentral/images/acts/" + imageFileName; + Element imageNode = doc.createElementNS(SVG_NAMESPACE, "image"); + imageNode.setAttributeNS(null, "id", "image-" + activityId); + imageNode.setAttributeNS(null, "x", Integer.toString(imageX)); + imageNode.setAttributeNS(null, "y", Integer.toString(imageY)); + imageNode.setAttributeNS(SVG_NAMESPACE_XLINK, "xlink:href", imageFileName); + imageNode.setAttributeNS(null, "width", Integer.toString(30)); + imageNode.setAttributeNS(null, "height", Integer.toString(30)); + g.appendChild(imageNode); + } + + } + + private void createRectangle(String id, double x, double y, Integer width, Integer height, String style, Element g) { + + if (style == null) { + style = ""; + } + + Element rectangle = doc.createElementNS(SVG_NAMESPACE, "rect"); + if (id != null) { + rectangle.setAttributeNS(null, "id", id); + } + rectangle.setAttributeNS(null, "x", Double.toString(x)); + rectangle.setAttributeNS(null, "y", Double.toString(y)); + rectangle.setAttributeNS(null, "width", Double.toString(width)); + rectangle.setAttributeNS(null, "height", Double.toString(height)); + rectangle.setAttributeNS(null, "style", style); + g.appendChild(rectangle); + } + + private void createGroupingEffect(String id, double x, double y, double width, double height, String style, + Element g) { + + Element groupingRectangle = doc.createElementNS(SVG_NAMESPACE, "rect"); + + groupingRectangle.setAttributeNS(null, "id", id); + groupingRectangle.setAttributeNS(null, "x", Double.toString(x + 4)); + groupingRectangle.setAttributeNS(null, "y", Double.toString(y + 4)); + groupingRectangle.setAttributeNS(null, "width", Double.toString(width)); + groupingRectangle.setAttributeNS(null, "height",Double.toString( height)); + groupingRectangle.setAttributeNS(null, "style", style + ";stroke:#3b3b3b;stroke-width:3"); + + g.appendChild(groupingRectangle); + } + + private void createText(String id, double x, double y, String dy, String textAnchor, String fontSize, + String fontFamily, String style, String text, Element g) { + + if (dy == null) { + dy = "0"; + } + if (textAnchor == null) { + textAnchor = "start"; + } + if (fontSize == null) { + fontSize = "11.4"; + } + if (fontFamily == null) { + fontFamily = "Verdana"; + } + + //trim text to fit into container + if (text.length() > 21) { + text = text.substring(0, 20); + } + + Element textNode = doc.createElementNS(SVG_NAMESPACE, "text"); + Node textContent = doc.createTextNode(text); + textNode.appendChild(textContent); + + textNode.setAttributeNS(null, "id", id); + textNode.setAttributeNS(null, "x", "" + x); + textNode.setAttributeNS(null, "y", "" + y); + textNode.setAttributeNS(null, "dy", dy); + textNode.setAttributeNS(null, "text-anchor", textAnchor); + textNode.setAttributeNS(null, "font-size", fontSize); + textNode.setAttributeNS(null, "font-family", fontFamily); + if (style != null) { + textNode.setAttributeNS(null, "style", style); + } + + g.appendChild(textNode); + } + + public static void main(String[] args) throws JDOMException, IOException { + + if (args.length != 1) { + System.err.println("Usage: java SVGGenerator fullFilePath"); + System.exit(1); + } + + String fullFilePath = args[0]; + + // import learning design + LearningDesignDTO learningDesign = (LearningDesignDTO) FileUtil.getObjectFromXML(null, fullFilePath); + + SVGGenerator svgGenerator = SVGGenerator.getInstance(); + svgGenerator.generateSvg(learningDesign); + +// // Stream out svg document to display +// OutputFormat format = new OutputFormat(svgGenerator.getSVGDocument()); +// format.setLineWidth(65); +// format.setIndenting(true); +// format.setIndent(2); +// Writer out = new StringWriter(); +// XMLSerializer serializer = new XMLSerializer(out, format); +// serializer.serialize(svgGenerator.getSVGDocument()); +// System.out.println(out.toString()); + + OutputFormat format = new OutputFormat(svgGenerator.getSVGDocument()); + format.setLineWidth(65); + format.setIndenting(true); + format.setIndent(2); + // Create file + String svgFileName = FileUtil.getFileName(fullFilePath); + String fileExtension = FileUtil.getFileExtension(svgFileName); + svgFileName = svgFileName.replaceFirst(fileExtension + "$", "svg"); + String svgFileFullPath = FileUtil.getFullPath(FileUtil.getFileDirectory(fullFilePath), svgFileName); + FileWriter fstream = new FileWriter(svgFileFullPath); + BufferedWriter out = new BufferedWriter(fstream); + XMLSerializer serializer = new XMLSerializer(out, format); + serializer.serialize(svgGenerator.getSVGDocument()); + System.out.println("Creating a file " + svgFileFullPath ); + // Close the output stream + out.close(); + } + +} Index: lams_common/src/java/org/lamsfoundation/lams/util/svg/SVGTrigonometryUtils.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/util/svg/SVGTrigonometryUtils.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/util/svg/SVGTrigonometryUtils.java (revision ef201100ffd7ebdfcb18c72528f062379e4c65d3) @@ -0,0 +1,129 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + +/* $Id$ */ +package org.lamsfoundation.lams.util.svg; + +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; + + +/** + * @author Andrey Balan + */ +public class SVGTrigonometryUtils { + + /** + * @param rectangleActivity we draw rectangle based on this activity + * @param toActivity draw transition to this activity + * @return + */ + public static Point2D getRectangleAndLineSegmentIntersection(ActivityTreeNode rectangleActivity, ActivityTreeNode toActivity) { + //rectangles dimensions + int rectangleX = rectangleActivity.getActivityCoordinates().x; + int rectangleY = rectangleActivity.getActivityCoordinates().y; + int width = rectangleActivity.getActivityDimension().width; + int height = rectangleActivity.getActivityDimension().height; + + //construct rectangle's lines + Line2D topLine = new Line2D.Double(rectangleX, rectangleY, rectangleX + width, rectangleY); + Line2D rightLine = new Line2D.Double(rectangleX + width, rectangleY, rectangleX + width, rectangleY + height); + Line2D bottomLine = new Line2D.Double(rectangleX, rectangleY + height, rectangleX + width, rectangleY + height); + Line2D leftLine = new Line2D.Double(rectangleX, rectangleY, rectangleX, rectangleY + height); + + //calculate MiddlePoint of the second rectangle + int transitionToX = toActivity.getActivityCoordinates().x + toActivity.getActivityDimension().width /2; + int transitionToY = toActivity.getActivityCoordinates().y + toActivity.getActivityDimension().height /2; + + Line2D transitionLine = new Line2D.Double(rectangleX + width/2, rectangleY + height/2, transitionToX, transitionToY); + + Point2D intersectionPoint = null; + if (isLineSegmentsIntersect(topLine, transitionLine)) { + intersectionPoint = getLinesIntersection(topLine, transitionLine); + } else if (isLineSegmentsIntersect(rightLine, transitionLine)) { + intersectionPoint = getLinesIntersection(rightLine, transitionLine); + } else if (isLineSegmentsIntersect(bottomLine, transitionLine)) { + intersectionPoint = getLinesIntersection(bottomLine, transitionLine); + } else if (isLineSegmentsIntersect(leftLine, transitionLine)) { + intersectionPoint = getLinesIntersection(leftLine, transitionLine); + } + + return intersectionPoint; + } + + /** + * Computes the intersection between first line (x1, y1)--(x2, y2) and second one (x3, y3)--(x4, y4) + * + * @return Point where the lines intersect, or null if they don't + */ + private static Point2D getLinesIntersection(Line2D line1, Line2D line2) { + double x1 = line1.getX1(); + double y1 = line1.getY1(); + double x2 = line1.getX2(); + double y2 = line1.getY2(); + double x3 = line2.getX1(); + double y3 = line2.getY1(); + double x4 = line2.getX2(); + double y4 = line2.getY2(); + + double d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); + if (d == 0) + return null; + + double xi = ((x3 - x4) * (x1 * y2 - y1 * x2) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d; + double yi = ((y3 - y4) * (x1 * y2 - y1 * x2) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d; + + return new Point2D.Double(xi, yi); + } + + /** Do line segments (x1, y1)--(x2, y2) and (x3, y3)--(x4, y4) intersect? */ + private static boolean isLineSegmentsIntersect(Line2D line1, Line2D line2) { + double x1 = line1.getX1(); + double y1 = line1.getY1(); + double x2 = line1.getX2(); + double y2 = line1.getY2(); + double x3 = line2.getX1(); + double y3 = line2.getY1(); + double x4 = line2.getX2(); + double y4 = line2.getY2(); + + int d1 = computeDirection(x3, y3, x4, y4, x1, y1); + int d2 = computeDirection(x3, y3, x4, y4, x2, y2); + int d3 = computeDirection(x1, y1, x2, y2, x3, y3); + int d4 = computeDirection(x1, y1, x2, y2, x4, y4); + return (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) + || (d1 == 0 && isOnSegment(x3, y3, x4, y4, x1, y1)) || (d2 == 0 && isOnSegment(x3, y3, x4, y4, x2, y2)) + || (d3 == 0 && isOnSegment(x1, y1, x2, y2, x3, y3)) || (d4 == 0 && isOnSegment(x1, y1, x2, y2, x4, y4)); + } + + private static boolean isOnSegment(double xi, double yi, double xj, double yj, double xk, double yk) { + return (xi <= xk || xj <= xk) && (xk <= xi || xk <= xj) && (yi <= yk || yj <= yk) && (yk <= yi || xk <= yj); + } + + private static int computeDirection(double xi, double yi, double xj, double yj, double xk, double yk) { + double a = (xk - xi) * (yj - yi); + double b = (xj - xi) * (yk - yi); + + return a < b ? -1 : a > b ? 1 : 0; + } + +}