/* * File Document.java * * Copyright (C) 2010 Remco Bouckaert remco@cs.auckland.ac.nz * * This file is part of BEAST2. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership and licensing. * * BEAST is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * BEAST 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with BEAST; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301 USA */ package beast.app.draw; import java.awt.Color; import java.io.File; import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Random; import java.util.Scanner; import java.util.Set; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import beast.core.BEASTInterface; import beast.core.Input; import beast.core.Runnable; import beast.core.util.Log; import beast.util.AddOnManager; import beast.util.XMLParser; import beast.util.XMLProducer; /** * The Document class is the Document part in the doc-view pattern of * the Beast ModelBuilder application. */ public class Document { /** * list of PluginShapes, InputShapes and connecting Arrows * */ public List<Shape> m_objects = new ArrayList<>(); public List<Shape> m_tmpobjects; /** * undo/redo related stuff * */ List<UndoAction> m_undoStack = new ArrayList<>(); int m_nCurrentEditAction = -1; //int m_nSavedPointer = -1; public boolean m_bIsSaved = true; Set<String> tabulist; public void isSaved() { m_bIsSaved = true; } void sChanged() { m_bIsSaved = false; } /** * list of class names for plug-ins to choose from * */ String[] m_sPlugInNames; // if false, only non-null and non-default valued inputs are shown // Also inputs from the tabu list will be eliminated boolean m_bShowALlInputs = false; boolean showAllInputs() { return m_bShowALlInputs; } boolean m_bSanitiseIDs = true; boolean sanitiseIDs() { return m_bSanitiseIDs; } public Document() { // load all parsers List<String> plugInNames = AddOnManager.find(beast.core.BEASTInterface.class, AddOnManager.IMPLEMENTATION_DIR); m_sPlugInNames = plugInNames.toArray(new String[0]); tabulist = new HashSet<>(); Properties properties = new Properties(); try { properties.load(getClass().getResourceAsStream("/beast/app/draw/tabulist.properties")); String list = properties.getProperty("tabulist"); for (String str : list.split("\\s+")) { tabulist.add(str.trim()); } } catch (Exception e) { e.printStackTrace(); } } // c'tor // /** change order of shapes to ensure arrows are drawn before the rest **/ // void moveArrowsToBack() { // ArrayList<Shape> arrows = new ArrayList<>(); // List<Shape> others = new ArrayList<>(); // // for (Shape shape : m_objects) { // if (shape instanceof Arrow) { // arrows.add(shape); // } else { // others.add(shape); // } // } // arrows.addAll(others); // m_objects = arrows; // } /** * adjust position of inputs to fit with the associated plug in * */ void adjustInputs() { for (Shape shape : m_objects) { if (shape instanceof BEASTObjectShape) { ((BEASTObjectShape) shape).adjustInputs(); } } } /** * adjust position of tail and head of arrows to the * Plug in and input shapes they are attached to * * */ void adjustArrows() { // adjustArrows(m_objects); // } // // void adjustArrows(List<Shape> objects) { for (Shape shape : m_objects) { if (shape instanceof Arrow) { Arrow arrow = (Arrow) shape; arrow.adjustCoordinates(); } } } // adjustArrows void readjustArrows(List<Shape> objects) { for (Shape shape : m_objects) { if (shape instanceof Arrow) { Arrow arrow = (Arrow) shape; arrow.m_sHeadID = arrow.m_headShape.getID(); ; arrow.m_sTailID = arrow.m_tailShape.getID(); ; } } } // readjustArrows /** * edit actions on shapes * */ public void moveShape(int x, int y, int toX, int toY, int position) { boolean needsUndoAction = true; if (m_nCurrentEditAction == m_undoStack.size() - 1 && m_nCurrentEditAction >= 0) { UndoAction undoAction = m_undoStack.get(m_nCurrentEditAction); if (undoAction.m_nActionType == UndoAction.MOVE_ACTION && undoAction.isSingleSelection(position)) { needsUndoAction = false; } } if (needsUndoAction) { addUndoAction(new UndoAction(position, UndoAction.MOVE_ACTION)); } Shape shape = m_objects.get(position); shape.movePosition(x, y, toX, toY); adjustArrows(); } // moveShape public void moveShapes(int dX, int dY, List<Integer> positions) { boolean needsUndoAction = true; if (m_nCurrentEditAction == m_undoStack.size() - 1 && m_nCurrentEditAction >= 0) { UndoAction undoAction = m_undoStack.get(m_nCurrentEditAction); if (undoAction.m_nActionType == UndoAction.MOVE_ACTION && undoAction.isSelection(positions)) { needsUndoAction = false; } } if (needsUndoAction) { addUndoAction(new UndoAction(positions, UndoAction.MOVE_ACTION)); } } // moveShape public void movePoint(int point, int x, int y, int toX, int toY, int position) { boolean needsUndoAction = true; if (m_nCurrentEditAction == m_undoStack.size() - 1 && m_nCurrentEditAction >= 0) { UndoAction undoAction = m_undoStack.get(m_nCurrentEditAction); if (undoAction.m_nActionType == UndoAction.RESHAPE_ACTION && undoAction.isSingleSelection(position)) { needsUndoAction = false; } } if (needsUndoAction) { addUndoAction(new UndoAction(position, UndoAction.RESHAPE_ACTION)); } Shape shape = m_objects.get(position); shape.movePoint(point, x, y, toX, toY); adjustArrows(); } // movePoint boolean containsID(String id, List<Shape> objects, List<String> tabulist) { for (Shape shape : m_objects) { if (shape.getID().equals(id)) { return true; } // if (shape instanceof Group) { // Group group = (Group) shape; // if (containsID(id, group.m_objects, tabulist)) { // return true; // } // } } if (tabulist == null) { return false; } for (String tabuID : tabulist) { if (tabuID.equals(id)) { return true; } } return false; } String getNewID(List<String> tabulist) { int _id = m_objects.size(); String id = "id" + _id; while (containsID(id, m_objects, tabulist)) { _id++; id = "id" + _id; } return id; } void setPluginID(BEASTObjectShape shape) { if (shape.m_beastObject.getID() != null && shape.m_beastObject.getID().length() > 0) { return; } BEASTInterface beastObject = shape.m_beastObject; String base = beastObject.getClass().getName().replaceAll(".*\\.", ""); int _id = 0; while (containsID(base + _id, m_objects, null)) { _id++; } beastObject.setID(base + _id); } Shape getShapeByID(String id) { for (Shape shape : m_objects) { if (shape.getID().equals(id)) { return shape; } } return null; } public void addNewShape(Shape shape) { if (shape.getID() == null || shape.getID().equals("") || containsID(shape.getID(), m_objects, null)) { if (shape instanceof Arrow) { ((Arrow) shape).setID(getNewID(null)); } if (shape instanceof BEASTObjectShape) { setPluginID((BEASTObjectShape) shape); } } m_objects.add(shape); if (shape instanceof BEASTObjectShape) { List<Integer> objects = new ArrayList<>(); objects.add(m_objects.size() - 1); checkForOtherPluginShapes(objects, (BEASTObjectShape) shape); if (objects.size() == 1) { addUndoAction(new PluginAction(m_objects.size() - 1, UndoAction.ADD_PLUGIN_ACTION)); } else { addUndoAction(new MultiObjectAction(objects, UndoAction.ADD_GROUP_ACTION)); } } else if (shape instanceof Arrow) { addUndoAction(new ArrowAction(m_objects.size() - 1, UndoAction.ADD_ARROW_ACTION)); } } // addNewShape void checkForOtherPluginShapes(List<Integer> objects, BEASTObjectShape shape) { // check whether we need to create any input beastObjects try { List<Input<?>> inputs = shape.m_beastObject.listInputs(); for (Input<?> input : inputs) { if (input.get() instanceof BEASTInterface) { BEASTInterface beastObject = (BEASTInterface) input.get(); BEASTObjectShape beastObjectShape = new BEASTObjectShape(beastObject, this); beastObjectShape.m_x = Math.max(shape.m_x - DX, 0); beastObjectShape.m_y = shape.m_y; beastObjectShape.m_w = 100; beastObjectShape.m_h = 80; setPluginID(beastObjectShape); m_objects.add(beastObjectShape); objects.add(m_objects.size() - 1); Arrow arrow = new Arrow(beastObjectShape, shape, input.getName()); m_objects.add(arrow); objects.add(m_objects.size() - 1); // recurse checkForOtherPluginShapes(objects, beastObjectShape); } } } catch (Exception e) { e.printStackTrace(); } } List<Integer> getConnectedArrows(List<String> ids, List<Integer> selection) { for (int i = 0; i < m_objects.size(); i++) { Shape shape = m_objects.get(i); if (shape instanceof Arrow) { Arrow arrow = (Arrow) shape; for (int j = 0; j < ids.size(); j++) { if (arrow.m_sHeadID.startsWith(ids.get(j)) || arrow.m_sTailID.equals(ids.get(j))) { if (!selection.contains(new Integer(i))) { selection.add(new Integer(i)); } } } } } return selection; } List<String> getIncomingArrows(List<String> ids) { List<String> selection = new ArrayList<>(); for (int i = 0; i < m_objects.size(); i++) { Shape shape = m_objects.get(i); if (shape instanceof Arrow) { Arrow arrow = (Arrow) shape; for (int j = 0; j < ids.size(); j++) { if (arrow.m_sHeadID.equals(ids.get(j))) { if (!selection.contains(arrow.m_sTailID)) { selection.add(arrow.m_sTailID); } } } } } return selection; } List<String> getOutgoingArrows(List<String> ids) { List<String> selection = new ArrayList<>(); for (int i = 0; i < m_objects.size(); i++) { Shape shape = m_objects.get(i); if (shape instanceof Arrow) { Arrow arrow = (Arrow) shape; for (int j = 0; j < ids.size(); j++) { if (arrow.m_sTailID.equals(ids.get(j))) { if (!selection.contains(arrow.m_sTailID)) { selection.add(arrow.m_sTailID); } } } } } return selection; } public void deleteShapes(List<Integer> selection) { List<String> ids = new ArrayList<>(); for (int j = 0; j < selection.size(); j++) { ids.add(m_objects.get(selection.get(j).intValue()).getID()); } selection = getConnectedArrows(ids, selection); UndoAction action = new MultiObjectAction(selection, UndoAction.DEL_GROUP_ACTION); addUndoAction(action); action.redo(); } // deleteShape void ensureUniqueID(Shape shape, List<String> tabulist) { if (shape.getID() == null || shape.getID().equals("") || containsID(shape.getID(), m_objects, tabulist)) { if (shape instanceof Arrow) { ((Arrow) shape).setID(getNewID(tabulist)); } if (shape instanceof BEASTObjectShape) { setPluginID((BEASTObjectShape) shape); } } tabulist.add(shape.getID()); } // ensureUniqueID public void pasteShape(String xml) { List<Shape> shapes = XML2Shapes(xml, true); if (shapes.size() == 0) { return; } List<Integer> positions = new ArrayList<>(); for (Shape shape : shapes) { if (shape instanceof Arrow) { ((Arrow) shape).setID(getNewID(null)); } if (shape instanceof BEASTObjectShape) { ((BEASTObjectShape) shape).m_beastObject.setID(null); setPluginID((BEASTObjectShape) shape); // ensure the new shape does not overlap exactly with an existing shape int offset = 0; boolean isMatch = false; do { isMatch = false; for (Shape shape2 : m_objects) { if (shape2.m_x == shape.m_x + offset && shape2.m_y == shape.m_y + offset && shape2.m_w == shape.m_w && shape2.m_h == shape.m_h) { isMatch = true; offset += 10; } } } while (isMatch); shape.m_x += offset; shape.m_y += offset; } m_objects.add(shape); positions.add(m_objects.size() - 1); } addUndoAction(new MultiObjectAction(positions, UndoAction.ADD_GROUP_ACTION)); } // pasteShape /** * move all plug ins connected with selection * */ public void collapse(Selection selection) { // // don't group arrows // for (int i = selection.m_Selection.size() - 1; i >= 0; i--) { // if ((Shape) m_objects.get(((Integer) selection.m_Selection.get(i)).intValue()) instanceof Arrow) { // selection.m_Selection.remove(i); // } // } // int nrOfPrimePositions = selection.m_Selection.size(); // if (nrOfPrimePositions == 0) { // return; // } // for (int i = 0; i < nrOfPrimePositions; i++) { // Shape shape = m_objects.get(((Integer) selection.m_Selection.get(i)).intValue()); // findAffectedShapes(shape, selection.m_Selection); // } // if (selection.m_Selection.size() == nrOfPrimePositions) { // // nothing to collapse // return; // } // // UndoAction action = new UndoMultiSelectionAction(selection.m_Selection, nrOfPrimePositions); // addUndoAction(action); // action.redo(); // selection.clear(); // selection.m_Selection.add(new Integer(m_objects.size() - 1)); // adjustInputs(); // adjustArrows(); } // collapse /** * Find inputs to collapse, i.e. PlugInShapes connected to * any input of a given PluginShape. * Shape IDs are recorded in selection. * */ void findAffectedShapes(Shape shape, List<Integer> selection) { if (shape instanceof InputShape) { findInputs((InputShape) shape, selection); } else { for (InputShape ellipse : ((BEASTObjectShape) shape).m_inputs) { findInputs(ellipse, selection); } } } /** * Find inputs to collapse, i.e. PlugInShapes connected to * given InputShape. Shape IDs are recorded in selection. * */ void findInputs(InputShape ellipse, List<Integer> selection) { for (Shape shape : m_objects) { if (shape instanceof Arrow) { Arrow arrow = (Arrow) shape; if (arrow.m_sHeadID.equals(ellipse.getID())) { String tailID = arrow.m_sTailID; for (int i = 0; i < m_objects.size(); i++) { if (m_objects.get(i).getID().equals(tailID)) { selection.add(i); } } } } } } // public void ungroup(Selection selection) { // UngroupAction action = new UngroupAction(selection); // addUndoAction(action); // action.redo(); // int size = action.getGroupSize(); // selection.clear(); // for (int i = 0; i < size; i++) { // selection.m_Selection.add(new Integer(m_objects.size() - i - 1)); // } // } // ungroup public void setFillColor(Color color, Selection selection) { if (selection.isSingleSelection()) { addUndoAction(new UndoAction(selection.getSingleSelection(), UndoAction.FILL_COLOR_ACTION)); } else { addUndoAction(new UndoAction(selection.m_Selection, UndoAction.MOVE_ACTION)); } for (int i = 0; i < selection.m_Selection.size(); i++) { int selectionIndex = selection.m_Selection.get(i).intValue(); Shape shape = m_objects.get(selectionIndex); shape.setFillColor(color); } } // setFillColor public void setPenColor(Color color, Selection selection) { if (selection.isSingleSelection()) { addUndoAction(new UndoAction(selection.getSingleSelection(), UndoAction.PEN_COLOR_ACTION)); } else { addUndoAction(new UndoAction(selection.m_Selection, UndoAction.MOVE_ACTION)); } for (int i = 0; i < selection.m_Selection.size(); i++) { int selectionIndex = selection.m_Selection.get(i).intValue(); Shape shape = m_objects.get(selectionIndex); shape.setPenColor(color); } } // setPenColor // public void addUndoGroupAction(Selection selection) { // addUndoAction(new UndoAction(selection.m_Selection)); // } int getPositionX(int shapeIndex) { Shape shape = m_objects.get(shapeIndex); return shape.getX(); } int getPositionY(int shapeIndex) { Shape shape = m_objects.get(shapeIndex); return shape.getY(); } int getPositionX2(int shapeIndex) { Shape shape = m_objects.get(shapeIndex); return shape.getX2(); } int getPositionY2(int shapeIndex) { Shape shape = m_objects.get(shapeIndex); return shape.getY2(); } void setPositionX(int x, int shapeIndex) { Shape shape = m_objects.get(shapeIndex); shape.setX(x); } void setPositionY(int y, int shapeIndex) { Shape shape = m_objects.get(shapeIndex); shape.setY(y); } void setPositionX2(int x, int shapeIndex) { Shape shape = m_objects.get(shapeIndex); shape.setX2(x); } void setPositionY2(int y, int shapeIndex) { Shape shape = m_objects.get(shapeIndex); shape.setY2(y); } public void setID(String id, int object) { addUndoAction(new UndoAction(object, UndoAction.SET_LABEL_ACTION)); Shape shape = m_objects.get(object); ((BEASTObjectShape) shape).m_beastObject.setID(id); } public void toggleFilled(int object) { addUndoAction(new UndoAction(object, UndoAction.TOGGLE_FILL_ACTION)); Shape shape = m_objects.get(object); shape.toggleFilled(); } void setInputValue(BEASTObjectShape beastObjectShape, String input, String valueString) { addUndoAction(new SetInputAction(beastObjectShape, input, valueString)); //beastObjectShape.m_beastObject.setInputValue(input, valueString); } /** * action representing assignment of value to a primitive input * */ class SetInputAction extends UndoAction { BEASTObjectShape m_beastObjectShape; String m_sInput; String m_sValue; SetInputAction(BEASTObjectShape beastObjectShape, String input, String valueString) { m_beastObjectShape = beastObjectShape; m_sInput = input; m_sValue = valueString; doit(); } @Override void redo() { doit(); } @Override void undo() { doit(); } @Override void doit() { try { String valueString = m_beastObjectShape.m_beastObject.getInput(m_sInput).get().toString(); m_beastObjectShape.m_beastObject.setInputValue(m_sInput, m_sValue); m_sValue = valueString; } catch (Exception e) { e.printStackTrace(); } } } // class SetInputAction public void toFront(Selection selection) { if (selection.m_Selection.size() == 0) { return; } int[] newOrder = new int[selection.m_Selection.size()]; for (int i = 0; i < newOrder.length; i++) { newOrder[i] = m_objects.size() - 1 - i; } int[] order = new int[selection.m_Selection.size()]; for (int i = 0; i < order.length; i++) { order[i] = selection.m_Selection.get(i).intValue(); } Arrays.sort(order); int[] oldOrder = new int[selection.m_Selection.size()]; for (int i = 0; i < oldOrder.length; i++) { oldOrder[i] = order[oldOrder.length - 1 - i]; } ReorderAction reorderAction = new ReorderAction(oldOrder, newOrder); addUndoAction(reorderAction); reorderAction.reorder(oldOrder, newOrder); selection.setSelection(newOrder); } // toFront public void toBack(Selection selection) { if (selection.m_Selection.size() == 0) { return; } int[] newOrder = new int[selection.m_Selection.size()]; for (int i = 0; i < selection.m_Selection.size(); i++) { newOrder[i] = i; } int[] oldOrder = new int[selection.m_Selection.size()]; for (int i = 0; i < oldOrder.length; i++) { oldOrder[i] = selection.m_Selection.get(i).intValue(); } Arrays.sort(oldOrder); ReorderAction reorderAction = new ReorderAction(oldOrder, newOrder); addUndoAction(reorderAction); reorderAction.reorder(oldOrder, newOrder); selection.setSelection(newOrder); } // toBack public void forward(Selection selection) { if (selection.m_Selection.size() == 0) { return; } int[] order = new int[selection.m_Selection.size()]; for (int i = 0; i < order.length; i++) { order[i] = selection.m_Selection.get(i).intValue(); } Arrays.sort(order); int[] oldOrder = new int[selection.m_Selection.size()]; for (int i = 0; i < oldOrder.length; i++) { oldOrder[i] = order[oldOrder.length - 1 - i]; } int[] newOrder = new int[selection.m_Selection.size()]; for (int i = 0; i < newOrder.length; i++) { if (oldOrder[i] < m_objects.size() - 1) { newOrder[i] = oldOrder[i] + 1; } else { newOrder[i] = m_objects.size() - 1; } } ReorderAction reorderAction = new ReorderAction(oldOrder, newOrder); addUndoAction(reorderAction); reorderAction.reorder(oldOrder, newOrder); selection.setSelection(newOrder); } // forward public void backward(Selection selection) { if (selection.m_Selection.size() == 0) { return; } int[] oldOrder = new int[selection.m_Selection.size()]; for (int i = 0; i < oldOrder.length; i++) { oldOrder[i] = selection.m_Selection.get(i).intValue(); } Arrays.sort(oldOrder); int[] newOrder = new int[selection.m_Selection.size()]; for (int i = 0; i < selection.m_Selection.size(); i++) { if (oldOrder[i] > 0) { newOrder[i] = oldOrder[i] - 1; } } ReorderAction reorderAction = new ReorderAction(oldOrder, newOrder); addUndoAction(reorderAction); reorderAction.reorder(oldOrder, newOrder); selection.setSelection(newOrder); } // backward /** * align set of nodes with the left most node in the list * * @param selection a selection */ public void alignLeft(Selection selection) { // update undo stack addUndoAction(new UndoAction(selection.m_Selection, UndoAction.MOVE_ACTION)); List<Integer> nodes = selection.m_Selection; int minX = -1; for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++) { int x = getPositionX(nodes.get(nodeIndex).intValue()); if (x < minX || nodeIndex == 0) { minX = x; } } for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++) { int node = nodes.get(nodeIndex).intValue(); setPositionX(minX, node); } adjustArrows(); } // alignLeft /** * align set of nodes with the right most node in the list * * @param selection a selection */ public void alignRight(Selection selection) { // update undo stack addUndoAction(new UndoAction(selection.m_Selection, UndoAction.MOVE_ACTION)); List<Integer> nodes = selection.m_Selection; int maxX = -1; for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++) { int x = getPositionX2(nodes.get(nodeIndex).intValue()); if (x > maxX || nodeIndex == 0) { maxX = x; } } for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++) { int node = nodes.get(nodeIndex).intValue(); int dX = getPositionX2(node) - getPositionX(node); setPositionX(maxX - dX, node); } adjustArrows(); } // alignRight /** * align set of nodes with the top most node in the list * * @param selection a selection */ public void alignTop(Selection selection) { // update undo stack addUndoAction(new UndoAction(selection.m_Selection, UndoAction.MOVE_ACTION)); List<Integer> nodes = selection.m_Selection; int minY = -1; for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++) { int y = getPositionY(nodes.get(nodeIndex).intValue()); if (y < minY || nodeIndex == 0) { minY = y; } } for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++) { int node = nodes.get(nodeIndex).intValue(); setPositionY(minY, node); } adjustArrows(); } // alignTop /** * align set of nodes with the bottom most node in the list * * @param selection a selection */ public void alignBottom(Selection selection) { // update undo stack addUndoAction(new UndoAction(selection.m_Selection, UndoAction.MOVE_ACTION)); List<Integer> nodes = selection.m_Selection; int maxY = -1; for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++) { int y = getPositionY2(nodes.get(nodeIndex).intValue()); if (y > maxY || nodeIndex == 0) { maxY = y; } } for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++) { int node = nodes.get(nodeIndex).intValue(); int dY = getPositionY2(node) - getPositionY(node); setPositionY(maxY - dY, node); } adjustArrows(); } // alignBottom /** * centre set of nodes half way between left and right most node in the list * * @param selection a selection */ public void centreHorizontal(Selection selection) { // update undo stack addUndoAction(new UndoAction(selection.m_Selection, UndoAction.MOVE_ACTION)); List<Integer> nodes = selection.m_Selection; int minY = -1; int maxY = -1; for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++) { int y = (getPositionY(nodes.get(nodeIndex).intValue()) + getPositionY2(nodes.get(nodeIndex).intValue())) / 2; if (y < minY || nodeIndex == 0) { minY = y; } if (y > maxY || nodeIndex == 0) { maxY = y; } } for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++) { int node = nodes.get(nodeIndex).intValue(); int dY = (getPositionY2(node) - getPositionY(node)) / 2; setPositionY((minY + maxY) / 2 - dY, node); } adjustArrows(); } // centreHorizontal /** * centre set of nodes half way between top and bottom most node in the list * * @param selection a selection */ public void centreVertical(Selection selection) { // update undo stack addUndoAction(new UndoAction(selection.m_Selection, UndoAction.MOVE_ACTION)); List<Integer> nodes = selection.m_Selection; int minX = -1; int maxX = -1; for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++) { int x = (getPositionX(nodes.get(nodeIndex).intValue()) + getPositionX2(nodes.get(nodeIndex).intValue())) / 2; if (x < minX || nodeIndex == 0) { minX = x; } if (x > maxX || nodeIndex == 0) { maxX = x; } } for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++) { int node = nodes.get(nodeIndex).intValue(); int dX = (getPositionX2(node) - getPositionX(node)) / 2; setPositionX((minX + maxX) / 2 - dX, node); } adjustArrows(); } // centreVertical /** * space out set of nodes evenly between left and right most node in the list * * @param selection a selection */ public void spaceHorizontal(Selection selection) { // update undo stack addUndoAction(new UndoAction(selection.m_Selection, UndoAction.MOVE_ACTION)); List<Integer> nodes = selection.m_Selection; int minX = -1; int maxX = -1; for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++) { int x = getPositionX(nodes.get(nodeIndex).intValue()); if (x < minX || nodeIndex == 0) { minX = x; } if (x > maxX || nodeIndex == 0) { maxX = x; } } for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++) { int node = nodes.get(nodeIndex).intValue(); setPositionX((int) (minX + nodeIndex * (maxX - minX) / (nodes.size() - 1.0)), node); } adjustArrows(); } // spaceHorizontal /** * space out set of nodes evenly between top and bottom most node in the list * * @param selection a selection */ public void spaceVertical(Selection selection) { // update undo stack addUndoAction(new UndoAction(selection.m_Selection, UndoAction.MOVE_ACTION)); List<Integer> nodes = selection.m_Selection; int minY = -1; int maxY = -1; for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++) { int y = getPositionY(nodes.get(nodeIndex).intValue()); if (y < minY || nodeIndex == 0) { minY = y; } if (y > maxY || nodeIndex == 0) { maxY = y; } } for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++) { int node = nodes.get(nodeIndex).intValue(); setPositionY((int) (minY + nodeIndex * (maxY - minY) / (nodes.size() - 1.0)), node); } adjustArrows(); } // spaceVertical public class UndoAction { final static int UNDO_ACTION = 0; final static int MOVE_ACTION = 1; final static int RESHAPE_ACTION = 2; final static int ADD_PLUGIN_ACTION = 3; final static int DEL_PLUGIN_ACTION = 4; final static int ADD_ARROW_ACTION = 5; final static int DEL_ARROW_ACTION = 6; final static int FILL_COLOR_ACTION = 5; final static int PEN_COLOR_ACTION = 6; final static int SET_LABEL_ACTION = 7; final static int TOGGLE_FILL_ACTION = 10; final static int ADD_GROUP_ACTION = 20; final static int DEL_GROUP_ACTION = 21; int m_nActionType = UNDO_ACTION; String m_sXML; List<Integer> m_nPositions; /* single selection undo actions **/ public UndoAction(int selection, int actionType) { if (!(m_objects.get(selection) instanceof BEASTObjectShape)) { return; } m_nActionType = actionType; m_nPositions = new ArrayList<>(); m_nPositions.add(selection); init(); } /* multiple selection undo actions **/ public UndoAction(List<Integer> selection, int actionType) { m_nActionType = actionType; m_nPositions = new ArrayList<>(); for (int i = 0; i < selection.size(); i++) { if (m_objects.get(selection.get(i)) instanceof BEASTObjectShape) { m_nPositions.add(new Integer(selection.get(i).intValue())); } } init(); } /* undo actions that don't need a selection **/ public UndoAction() { } boolean isSingleSelection(int position) { return (m_nPositions.size() == 1 && m_nPositions.get(0) == position); } boolean isSelection(List<Integer> positions) { int matches = 0; for (Integer i : positions) { if (m_objects.get(i) instanceof BEASTObjectShape) { if (m_nPositions.contains(i)) { matches++; } else { return false; } } } return matches == m_nPositions.size(); } void init() { m_sXML = "<doc>"; for (int i = 0; i < m_nPositions.size(); i++) { int shapeIndex = m_nPositions.get(i).intValue(); Shape shape = m_objects.get(shapeIndex); m_sXML += shape.getXML(); } m_sXML += "</doc>"; } void undo() { doit(); } void redo() { doit(); } void doit() { String xml = "<doc>"; for (int i = 0; i < m_nPositions.size(); i++) { int shapeIndex = m_nPositions.get(i).intValue(); Shape shape = m_objects.get(shapeIndex); xml += shape.getXML(); } xml += "</doc>"; List<Shape> shapes = XML2Shapes(m_sXML, false); for (int i = 0; i < m_nPositions.size(); i++) { int shapeIndex = m_nPositions.get(i).intValue(); Shape originalShape = m_objects.get(shapeIndex); Shape shape = shapes.get(i); ((BEASTObjectShape) shape).m_beastObject = ((BEASTObjectShape) originalShape).m_beastObject; originalShape.assignFrom(shape); } m_sXML = xml; } } // class UndoAction /** * action representing addition/deletion of a single beastObject. * This does not take connecting arrows in account. * Use MultiObjectAction to add/delete beastObject with its connecting arrows. */ class PluginAction extends UndoAction { public PluginAction(int position, int actionType) { // assumes beastObjectShape + all its inputs has just been added m_nActionType = actionType; BEASTObjectShape beastObjectShape = (BEASTObjectShape) m_objects.get(position); m_nPositions = new ArrayList<>(); m_nPositions.add(position); position--; while (position >= 0 && m_objects.get(position) instanceof InputShape && ((InputShape) m_objects.get(position)).getPluginShape() == beastObjectShape) { m_nPositions.add(0, position--); } // creat XML init(); } @Override void undo() { switch (m_nActionType) { case ADD_PLUGIN_ACTION: removePlugin(); return; case DEL_PLUGIN_ACTION: addPlugin(); return; } Log.err.println("Error 101: action type not set properly"); } @Override void redo() { switch (m_nActionType) { case ADD_PLUGIN_ACTION: addPlugin(); return; case DEL_PLUGIN_ACTION: removePlugin(); return; } Log.err.println("Error 102: action type not set properly"); } void removePlugin() { for (int i = m_nPositions.size() - 1; i >= 0; i--) { m_objects.remove((int) m_nPositions.get(i)); } } void addPlugin() { List<Shape> shapes = XML2Shapes(m_sXML, true); for (int i = 0; i < m_nPositions.size(); i++) { m_objects.add(m_nPositions.get(i), shapes.get(i)); } } } // class AddPluginAction /** * action representing addition/deletion of a single arrow. */ class ArrowAction extends UndoAction { public ArrowAction(int position, int arrowAction) { m_nActionType = arrowAction; m_nPositions = new ArrayList<>(); m_nPositions.add(position); init(); } @Override void undo() { switch (m_nActionType) { case ADD_ARROW_ACTION: removeArrow(); return; case DEL_ARROW_ACTION: addArrow(); return; } Log.err.println("Error 103: action type not set properly"); } @Override void redo() { switch (m_nActionType) { case ADD_ARROW_ACTION: addArrow(); return; case DEL_ARROW_ACTION: removeArrow(); return; } Log.err.println("Error 104: action type not set properly"); } void removeArrow() { Arrow arrow = (Arrow) m_objects.get(m_nPositions.get(0)); m_objects.remove((int) m_nPositions.get(0)); // unconnect plug-in and input final Input<?> input = arrow.m_headShape.m_input; if (input instanceof List<?>) { ((List<?>) input.get()).remove(arrow.m_tailShape.m_beastObject); } else { try { input.setValue(null, arrow.m_headShape.getBEASTObject()); } catch (Exception e) { e.printStackTrace(); } } } void addArrow() { List<Shape> shapes = XML2Shapes(m_sXML, true); Arrow arrow = (Arrow) shapes.get(0); m_objects.add(m_nPositions.get(0), arrow); // reconnect plug-in with input arrow.m_tailShape = getPluginShapeWithLabel(arrow.m_sTailID); arrow.m_headShape = getInputShapeWithID(arrow.m_sHeadID); try { arrow.m_headShape.m_input.setValue(arrow.m_tailShape.m_beastObject, arrow.m_headShape.getBEASTObject()); } catch (Exception e) { e.printStackTrace(); } } } // class ArrowAction /** * action representing addition or deletion of multiple beastObjects/arrows */ class MultiObjectAction extends UndoAction { List<UndoAction> m_actions; MultiObjectAction(List<Integer> positions, int actionType) { m_nActionType = actionType; m_actions = new ArrayList<>(); // remove duplicates, if any Collections.sort(positions, (Integer o1, Integer o2) -> { return (o2 - o1); } ); for (int i = 1; i < positions.size(); i++) { if (positions.get(i) == positions.get(i - 1)) { positions.remove(i); i--; } } // split in beastObjects and arrows List<Integer> arrows = new ArrayList<>(); List<Integer> pluginsShapes = new ArrayList<>(); for (int i : positions) { Shape shape = m_objects.get(i); if (shape instanceof BEASTObjectShape) { pluginsShapes.add(i); } else if (shape instanceof Arrow) { arrows.add(i); } } // create appropriate set of undo actions switch (actionType) { case ADD_GROUP_ACTION: for (int i : pluginsShapes) { m_actions.add(new PluginAction(i, ADD_PLUGIN_ACTION)); } for (int i : arrows) { m_actions.add(new ArrowAction(i, ADD_ARROW_ACTION)); } break; case DEL_GROUP_ACTION: for (int i : arrows) { m_actions.add(new ArrowAction(i, DEL_ARROW_ACTION)); } for (int i : pluginsShapes) { m_actions.add(new PluginAction(i, DEL_PLUGIN_ACTION)); } break; default: Log.err.println("Error 105: unrecognized action type"); } } @Override void undo() { for (int i = m_actions.size() - 1; i >= 0; i--) { m_actions.get(i).undo(); } } @Override void redo() { for (int i = 0; i < m_actions.size(); i++) { m_actions.get(i).redo(); } } } // class MultiObjectAction class ReorderAction extends UndoAction { int[] m_oldOrder; int[] m_newOrder; ReorderAction(int[] oldOrder, int[] newOrder) { super(); m_oldOrder = oldOrder; m_newOrder = newOrder; } @Override void undo() { reorder(m_oldOrder, m_newOrder); } @Override void redo() { for (int i = m_newOrder.length - 1; i >= 0; i--) { int selectionIndex = m_newOrder[i]; Shape shape = m_objects.get(selectionIndex); m_objects.remove(selectionIndex); m_objects.add(m_oldOrder[i], shape); } } void reorder(int[] oldOrder, int[] newOrder) { for (int i = 0; i < oldOrder.length; i++) { int selectionIndex = oldOrder[i]; Shape shape = m_objects.get(selectionIndex); m_objects.remove(selectionIndex); m_objects.add(newOrder[i], shape); } } } // class ReorderAction /** * add undo action to the undo stack. * * @param action operation that needs to be added to the undo stack */ void addUndoAction(UndoAction action) { int actionIndex = m_undoStack.size() - 1; while (actionIndex > m_nCurrentEditAction) { m_undoStack.remove(actionIndex--); } m_undoStack.add(action); m_nCurrentEditAction++; } // addUndoAction /** * remove all actions from the undo stack */ public void clearUndoStack() { m_undoStack = new ArrayList<>(); m_nCurrentEditAction = -1; } // clearUndoStack public boolean canUndo() { return m_nCurrentEditAction > -1; } // canUndo public boolean canRedo() { return m_nCurrentEditAction < m_undoStack.size() - 1; } // canRedo public void undo() { if (!canUndo()) { return; } UndoAction undoAction = m_undoStack.get(m_nCurrentEditAction); undoAction.undo(); adjustInputs(); recalcArrows(); adjustArrows(); m_nCurrentEditAction--; } // undo public void redo() { if (!canRedo()) { return; } m_nCurrentEditAction++; UndoAction undoAction = m_undoStack.get(m_nCurrentEditAction); undoAction.redo(); adjustInputs(); recalcArrows(); adjustArrows(); } // redo BEASTObjectShape getPluginShapeWithLabel(String label) { for (Shape shape : m_objects) { if (shape instanceof BEASTObjectShape) { if (shape.getLabel() != null && shape.getLabel().equals(label)) { return (BEASTObjectShape) shape; } } } return null; } InputShape getInputShapeWithID(String label) { for (Shape shape : m_objects) { if (shape instanceof InputShape) { if (shape.getID() != null && shape.getID().equals(label)) { return (InputShape) shape; } } } return null; } final static int DX = 120; final static int DY = 80; void addInput(BEASTObjectShape shape, Object o2, int depth, String input) throws InstantiationException, IllegalAccessException, ClassNotFoundException { if (o2 instanceof BEASTInterface) { BEASTObjectShape shape2 = getPluginShapeWithLabel(((BEASTInterface) o2).getID()); if (shape2 == null) { shape2 = new BEASTObjectShape((BEASTInterface) o2, this); shape2.m_x = depth * DX; shape2.m_w = DY; shape2.m_beastObject = (BEASTInterface) o2; setPluginID(shape2); m_objects.add(shape2); } process(shape2, depth); } } void process(BEASTObjectShape shape, int depth) throws IllegalArgumentException, IllegalAccessException, InstantiationException, ClassNotFoundException { BEASTInterface beastObject = shape.m_beastObject; List<Input<?>> inputs = beastObject.listInputs(); for (Input<?> input_ : inputs) { Object o = input_.get(); if (o != null) { if (o instanceof List<?>) { for (Object o2 : (List<?>) o) { addInput(shape, o2, depth + 1, input_.getName()); } } else if (o instanceof BEASTInterface) { addInput(shape, o, depth + 1, input_.getName()); // } else { // it is a primitive type } } } } public void loadFile(String fileName) { m_objects.clear(); XMLParser parser = new XMLParser(); try { //fileName; StringBuilder xml = new StringBuilder(); String NL = System.getProperty("line.separator"); Scanner scanner = new Scanner(new File(fileName)); try { while (scanner.hasNextLine()) { xml.append(scanner.nextLine() + NL); } } finally { scanner.close(); } BEASTInterface plugin0 = parser.parseBareFragment(xml.toString(), false); init(plugin0); } catch (Exception e) { e.printStackTrace(); // TODO: handle exception } } void reinit() { String xml = toXML(); m_objects.clear(); try { XMLParser parser = new XMLParser(); BEASTInterface plugin0 = parser.parseBareFragment(xml, false); init(plugin0); } catch (Exception e) { e.printStackTrace(); // TODO: handle exception } } public void init(BEASTInterface plugin0) { try { if (plugin0 instanceof BEASTObjectSet) { List<BEASTInterface> set = ((BEASTObjectSet) plugin0).m_plugins.get(); if (set == null) { return; } for (BEASTInterface beastObject : set) { BEASTObjectShape shape = new BEASTObjectShape(beastObject, this); shape.m_beastObject = beastObject; setPluginID(shape); m_objects.add(shape); process(shape, 1); shape.m_x = DX; shape.m_w = 100; Random random = new Random(); shape.m_y = random.nextInt(800); shape.m_h = 50; } } else { BEASTObjectShape shape = new BEASTObjectShape(plugin0, this); shape.m_beastObject = plugin0; setPluginID(shape); m_objects.add(shape); process(shape, 1); shape.m_x = DX; shape.m_w = 100; Random random = new Random(); shape.m_y = random.nextInt(800); shape.m_h = 50; } } catch (Exception e) { e.printStackTrace(); // TODO: handle exception } // clear undo stack after all items have been // added since the stack is affected by parsing... clearUndoStack(); recalcArrows(); layout(); adjustArrows(); } // init List<Shape> XML2Shapes(String xml, boolean reconstructBEASTObjects) { List<Shape> shapes = new ArrayList<>(); m_tmpobjects = shapes; try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(false); org.w3c.dom.Document doc = factory.newDocumentBuilder().parse(new org.xml.sax.InputSource(new StringReader(xml))); doc.normalize(); NodeList nodes = doc.getDocumentElement().getChildNodes(); for (int nodeIndex = 0; nodeIndex < nodes.getLength(); nodeIndex++) { Node node = nodes.item(nodeIndex); if (node.getNodeType() == Node.ELEMENT_NODE) { shapes.add(parseNode(node, this, reconstructBEASTObjects)); } } } catch (Throwable t) { } m_tmpobjects = null; return shapes; } final static String PLUGIN_SHAPE_ELEMENT = "pluginshape"; final static String INPUT_SHAPE_ELEMENT = "inputshape"; final static String ARROW_ELEMENT = "arrow"; /** * parse XDL xml format * */ static Shape parseNode(Node node, Document doc, boolean reconstructBEASTObjects) { Shape shape = null; if (node.getNodeName().equals(INPUT_SHAPE_ELEMENT) && reconstructBEASTObjects) { shape = new InputShape(node, doc, reconstructBEASTObjects); } else if (node.getNodeName().equals(ARROW_ELEMENT) && reconstructBEASTObjects) { shape = new Arrow(node, doc, reconstructBEASTObjects); } else if (node.getNodeName().equals(PLUGIN_SHAPE_ELEMENT)) { shape = new BEASTObjectShape(node, doc, reconstructBEASTObjects); } return shape; } // parseNode public String toXML() { XMLProducer xmlProducer = new XMLProducer(); BEASTObjectSet pluginSet = calcPluginSet(); if (pluginSet.m_plugins.get().size() == 1) { return xmlProducer.toXML(pluginSet.m_plugins.get().get(0)); } return xmlProducer.toXML(pluginSet); } // toXML /** * collect all objects and put all top-level beastObjects in a PluginSet */ BEASTObjectSet calcPluginSet() { // collect all plug-ins Collection<BEASTInterface> beastObjects = getPlugins(); // calc outputs HashMap<BEASTInterface, List<BEASTInterface>> outputs = BEASTObjectPanel.getOutputs(beastObjects); // put all beastObjects with no ouputs in the PluginSet BEASTObjectSet pluginSet = new BEASTObjectSet(); for (BEASTInterface beastObject : outputs.keySet()) { if (outputs.get(beastObject).size() == 0) { try { pluginSet.setInputValue("beastObject", beastObject); } catch (Exception e) { e.printStackTrace(); } } } return pluginSet; } // calcPluginSet /** * convert m_objects in set of beastObjects * */ Collection<BEASTInterface> getPlugins() { Collection<BEASTInterface> beastObjects = new HashSet<>(); for (Shape shape : m_objects) { if (shape instanceof BEASTObjectShape) { beastObjects.add(((BEASTObjectShape) shape).m_beastObject); } } return beastObjects; } /** * return true if source is ascendant of target * */ boolean isAscendant(BEASTInterface source, BEASTInterface target) { Collection<BEASTInterface> beastObjects = getPlugins(); List<BEASTInterface> ascendants = BEASTObjectPanel.listAscendants(target, beastObjects); return ascendants.contains(source); } Shape findObjectWithID(String id) { if (m_tmpobjects != null) { for (int i = 0; i < m_tmpobjects.size(); i++) { if (m_tmpobjects.get(i).getID().equals(id)) { return m_tmpobjects.get(i); } } } for (int i = 0; i < m_objects.size(); i++) { if (m_objects.get(i).getID().equals(id)) { return m_objects.get(i); } } return null; } /** * simple layout algorithm for placing PluginShapes * */ void layout() { // first construct input map for ease of navigation HashMap<BEASTObjectShape, List<BEASTObjectShape>> inputMap = new HashMap<>(); HashMap<BEASTObjectShape, List<BEASTObjectShape>> outputMap = new HashMap<>(); for (Shape shape : m_objects) { if (shape instanceof BEASTObjectShape && shape.m_bNeedsDrawing) { inputMap.put((BEASTObjectShape) shape, new ArrayList<>()); outputMap.put((BEASTObjectShape) shape, new ArrayList<>()); } } for (Shape shape : m_objects) { if (shape instanceof Arrow && shape.m_bNeedsDrawing) { Shape headShape = ((Arrow) shape).m_headShape; BEASTObjectShape beastObjectShape = ((InputShape) headShape).m_beastObjectShape; BEASTObjectShape inputShape = ((Arrow) shape).m_tailShape; inputMap.get(beastObjectShape).add(inputShape); outputMap.get(inputShape).add(beastObjectShape); } } // reset all x-coords to minimal x-value for (Shape shape : inputMap.keySet()) { shape.m_x = DX; } // move inputs rightward till they exceed x-coord of their inputs boolean progress = true; while (progress) { progress = false; for (Shape shape : inputMap.keySet()) { int maxInputX = -DX; for (Shape input : inputMap.get(shape)) { maxInputX = Math.max(maxInputX, input.m_x); } if (shape.m_x < maxInputX + DX) { shape.m_x = maxInputX + DX; progress = true; } } } // move inputs rightward till they are stopped by their outputs progress = true; while (progress) { progress = false; for (Shape shape : outputMap.keySet()) { int minOutputX = Integer.MAX_VALUE; for (Shape input : outputMap.get(shape)) { minOutputX = Math.min(minOutputX, input.m_x); } if (minOutputX < Integer.MAX_VALUE && shape.m_x < minOutputX - DX) { shape.m_x = minOutputX - DX; progress = true; } } } layoutAdjustY(inputMap); // relax a bit Log.warning.print("Relax..."); for (int i = 0; i < 250; i++) { relax(false); } Log.warning.println("Done"); layoutAdjustY(inputMap); adjustInputs(); } /** * Adjust y-coordinate of PluginShapes so they don't overlap * and are close to their inputs. Helper method for layout() * */ void layoutAdjustY(HashMap<BEASTObjectShape, List<BEASTObjectShape>> inputMap) { // next, optimise top down order boolean progress = true; int x = DX; while (progress) { List<BEASTObjectShape> shapes = new ArrayList<>(); // find shapes with same x-coordinate for (BEASTObjectShape shape : inputMap.keySet()) { if (shape.m_x == x) { shapes.add(shape); } } int k = 1; HashMap<Integer, BEASTObjectShape> ycoordMap = new HashMap<>(); // set y-coordinate as mean of inputs // if there are no inputs, order them top to bottom at DY intervals for (BEASTObjectShape shape : shapes) { List<BEASTObjectShape> inputs = inputMap.get(shape); if (inputs.size() == 0) { shape.m_y = k * DY; } else { shape.m_y = 0; for (Shape input : inputs) { shape.m_y += input.m_y; } shape.m_y /= inputs.size(); } while (ycoordMap.containsKey(shape.m_y)) { shape.m_y++; } ycoordMap.put(shape.m_y, shape); k++; } // ensure shapes are sufficiently far apart - at least DY between them int prevY = 0; ArrayList<Integer> yCoords = new ArrayList<>(); yCoords.addAll(ycoordMap.keySet()); Collections.sort(yCoords); int dY = 0; for (Integer i : yCoords) { BEASTObjectShape shape = ycoordMap.get(i); if (shape.m_y < prevY + DY) { dY = prevY + DY - shape.m_y; shape.m_y = prevY + DY; } prevY = shape.m_y; } // upwards correction if (dY > 0) { dY /= shapes.size(); for (BEASTObjectShape shape : shapes) { shape.m_y -= dY; } } progress = (shapes.size() > 0); x += DX; } } // layoutAdjustY /** * apply spring model algorithm to the placement of plug-in shapes * */ public void relax(boolean allowXToMove) { List<Shape> objects = new ArrayList<>(); for (Shape shape : m_objects) { if (shape.m_bNeedsDrawing) { objects.add(shape); } } // Step 0: determine degrees HashMap<String, Integer> degreeMap = new HashMap<>(); for (Shape shape : objects) { if (shape instanceof Arrow) { Arrow arrow = (Arrow) shape; String id = arrow.m_tailShape.getID(); if (arrow.m_headShape instanceof InputShape) { String id2 = arrow.m_headShape.m_beastObjectShape.getID(); if (degreeMap.containsKey(id)) { degreeMap.put(id, degreeMap.get(id) + 1); } else { degreeMap.put(id, 1); } if (degreeMap.containsKey(id2)) { degreeMap.put(id2, degreeMap.get(id2) + 1); } else { degreeMap.put(id2, 1); } } } } // Step 1: relax for (Shape shape : objects) { if (shape instanceof Arrow) { Arrow arrow = (Arrow) shape; Shape source = arrow.m_tailShape; int p1x = source.m_x + source.m_w / 2; int p1y = source.m_y + source.m_h / 2; if (arrow.m_headShape instanceof InputShape) { Shape target = arrow.m_headShape.m_beastObjectShape; int p2x = target.m_x + target.m_w / 2; int p2y = target.m_y + target.m_h / 2; double vx = p1x - p2x; double vy = p1y - p2y; double len = Math.sqrt(vx * vx + vy * vy); double desiredLen = 150; // round from zero, if needed [zero would be Bad.]. len = (len == 0) ? .0001 : len; double f = 1.0 / 3.0 * (desiredLen - len) / len; int degree1 = degreeMap.get(source.getID()); int degree2 = degreeMap.get(target.getID()); f = f * Math.pow(0.99, (degree1 + degree2 - 2)); // the actual movement distance 'dx' is the force multiplied by the // distance to go. double dx = Math.min(f * vx, 3); double dy = Math.min(f * vy, 3); if (vx > -200 && vx < 0) { dx = -dx; //f *= Math.abs((vx+200))/40; } if (allowXToMove) source.m_x = (int) Math.max(100, source.m_x + dx); source.m_y = (int) Math.max(10, source.m_y + dy); if (allowXToMove) target.m_x = (int) Math.max(100, target.m_x - dx); target.m_y = (int) Math.max(10, target.m_y - dy); } } } // Step 2: repulse (pairwise) for (Shape shape1 : objects) { if (shape1 instanceof BEASTObjectShape) { int p1x = shape1.m_x + shape1.m_w / 2; int p1y = shape1.m_y + shape1.m_h / 2; double dx = 0, dy = 0; for (Shape shape2 : objects) { if (shape2 instanceof BEASTObjectShape) { int p2x = shape2.m_x + shape2.m_w / 2; int p2y = shape2.m_y + shape2.m_h / 2; double vx = p1x - p2x; double vy = p1y - p2y; double distanceSq = Math.sqrt(vx * vx + vy * vy); if (distanceSq == 0) { dx += Math.random() * 1; dy += Math.random() * 5; } else if (distanceSq < 300 /*repulsion_range_sq*/) { double factor = 500; dx += factor * vx / distanceSq / distanceSq; dy += factor * vy / distanceSq / distanceSq; } } } if (allowXToMove) shape1.m_x = (int) Math.min(800, Math.max(10, shape1.m_x + dx)); shape1.m_y = (int) Math.min(800, Math.max(10, shape1.m_y + dy)); } } // Step 3: move dependent objects in place for (Shape shape : objects) { if (shape instanceof BEASTObjectShape) { ((BEASTObjectShape) shape).adjustInputs(); } } adjustArrows(); } // relax /** * sanity check: make sure there are no cycles and there is a Runnable * plug in that is not an input of another plug in */ final static int STATUS_OK = 0, STATUS_CYCLE = 1, STATUS_NOT_RUNNABLE = 2, STATUS_EMPTY_MODEL = 3, STATUS_ORPHANS_IN_MODEL = 4; int isValidModel() { BEASTObjectSet pluginSet = calcPluginSet(); if (pluginSet.m_plugins.get().size() == 0) { return STATUS_EMPTY_MODEL; } if (pluginSet.m_plugins.get().size() > 1) { return STATUS_ORPHANS_IN_MODEL; } boolean hasRunable = false; for (BEASTInterface beastObject : pluginSet.m_plugins.get()) { if (beastObject instanceof Runnable) { hasRunable = true; } } if (!hasRunable) { return STATUS_NOT_RUNNABLE; } return STATUS_OK; } // isValidModel /** * remove all arrows, then add based on the beastObject inputs * */ void recalcArrows() { // remove all arrows for (int i = m_objects.size() - 1; i >= 0; i--) { Shape shape = m_objects.get(i); if (shape instanceof Arrow) { m_objects.remove(i); } } // build map for quick resolution of PluginShapes HashMap<BEASTInterface, BEASTObjectShape> map = new HashMap<>(); for (Shape shape : m_objects) { if (shape instanceof BEASTObjectShape) { map.put(((BEASTObjectShape) shape).m_beastObject, (BEASTObjectShape) shape); } } // re-insert arrows, if any for (int i = m_objects.size() - 1; i >= 0; i--) { Shape shape = m_objects.get(i); if (shape instanceof BEASTObjectShape) { BEASTObjectShape headShape = ((BEASTObjectShape) shape); BEASTInterface beastObject = headShape.m_beastObject; try { List<Input<?>> inputs = beastObject.listInputs(); for (Input<?> input : inputs) { if (input.get() != null) { if (input.get() instanceof BEASTInterface) { BEASTObjectShape tailShape = map.get(input.get()); try { Arrow arrow = new Arrow(tailShape, headShape, input.getName()); arrow.setID(getNewID(null)); m_objects.add(arrow); } catch (Exception e) { // ignore, can happen when not all inputs are to be shown } } if (input.get() instanceof List<?>) { for (Object o : (List<?>) input.get()) { if (o != null && o instanceof BEASTInterface) { BEASTObjectShape tailShape = map.get(o); try { Arrow arrow = new Arrow(tailShape, headShape, input.getName()); arrow.setID(getNewID(null)); m_objects.add(arrow); } catch (Exception e) { // ignore, can happen when not all inputs are to be shown } } } } } } } catch (Exception e) { e.printStackTrace(); } } } //moveArrowsToBack(); } // recalcArrows } // class Document