package main; import java.awt.*; import java.util.*; import java.io.*; import affectors.Affector; import affectors.EnhancerRegionAff; import affectors.DecayAff; public class AffectorTemplate { Affector affector = null; String affectorNode; int intersectX = -1000, intersectY = -1000; String [] nodeNames = null; // This is used while constructing network, then it's turned into Nodes /** This is used while constructing a network to figure out which cell each node comes from. If you make a new affector that is internal, set sides = null. For membrane affectors, allocate one array position in sides for each node used in the affector and set sides[node_pos] = -1 if the node is in the same cell as that being changed by the affector, or the side (passed into the initializer of the affector) if the node is on the opposite cell. */ int [] sides = null; /** This is here to let input and output code (including the UI) figure out which parameters are being used by an affector. It contains a list of Integer objects corresponding to the parameters requested with getParamPos() from the initialization code. This vector is not used by the model running code. */ Vector itsParameters = new Vector(); Vector enhancerTemplates = new Vector(); public int affectorType = Affector.CONVERSION; static final float sinAngle = (float)Math.sin(35 * (2 * 3.14159) / 360 ); static final float cosAngle = (float)Math.cos(35 * (2 * 3.14159) / 360 ); static final float arrowLen = 8; public AffectorTemplate(String nodename) { affectorNode = nodename; } public Affector makeAffector(Cell cell, int side) throws Exception { Affector new_affector = affector.copy(); new_affector.setSide(side); // Note - sets sides for subaffectors as well fixNodes(new_affector, cell); return new_affector; } private void fixNodes(Affector new_affector, Cell cell) throws Exception { new_affector.fixNodes(cell, nodeNames); if(enhancerTemplates.size() > 0) { // Its a meta-affector, so need to fix nodes for all subaffectors as well. Enumeration enum = enhancerTemplates.elements(); int num = 0; while(enum.hasMoreElements()) { AffectorTemplate template = (AffectorTemplate)enum.nextElement(); Affector subaff = ((EnhancerRegionAff)new_affector).getSubaffector(num); template.fixNodes(subaff, cell); num++; } } } /** Returns an Enumeration on the enhancerTemplates Vector. If Enhancers are themselves Enhancers, then their lists are included. @author WJS */ public Enumeration getEnhancerTemplates() { Vector v = new Vector(); Enumeration en = enhancerTemplates.elements(); AffectorTemplate affectorTemplate; while (en.hasMoreElements()) { affectorTemplate = (AffectorTemplate)en.nextElement(); if (affectorTemplate.getAffector() instanceof EnhancerRegionAff) { Enumeration en2 = affectorTemplate.getEnhancerTemplates(); while (en2.hasMoreElements()) v.add((AffectorTemplate)en2.nextElement()); } else v.add(affectorTemplate); } return(v.elements()); } public void readSetup(BetterTokenizer tokenizer, String affector_name) throws Exception { int i; String [] strings = new String[20]; // Assume there will never be more than 20 strings as input to an affector boolean have_position = false; affector = createAffector(affector_name); // Read in the parameters int num_params = 0; while(GeneralInput.nextToken(tokenizer) == BetterTokenizer.TT_WORD || tokenizer.ttype == BetterTokenizer.TT_NUMBER || tokenizer.ttype == '"') { // read in affector arguments to end of line if(tokenizer.ttype == BetterTokenizer.TT_NUMBER) strings[num_params++] = Float.toString((float)tokenizer.nval); else strings[num_params++] = tokenizer.sval; } // System.out.println(affector_name + " " + strings[0] + " " + strings[1]); // Deal with meta affectors first and return if(affector instanceof EnhancerRegionAff) { readInteractionSet(tokenizer, "end" + affector_name); // Check whether last two parameters are numbers. If so, assume they are position if(num_params >= 2) { try { Float.valueOf(strings[num_params - 2]).floatValue(); Float.valueOf(strings[num_params - 1]).floatValue(); have_position = true; num_params -= 2; } catch(Exception e) {} // Must not have been numbers } } // Check that we have correct number of params - but not for meta-affectors, which might have variable numbers of parameters // Can also be 2 extra parameters, which are position of affector center point else { if(affector.getNumNodes() + affector.getNumParameters() != num_params && affector.getNumNodes() + affector.getNumParameters() != num_params - 2) { affector = null; throw new Exception("Error: Wrong number of parameters when making " + affector_name + " for " + strings[0]); } else if(affector.getNumNodes() + affector.getNumParameters() == num_params - 2) { num_params -= 2; have_position = true; } } // Store the names of the nodes if(affector.getNumNodes() > 0) { nodeNames = new String[affector.getNumNodes()]; for(i = 0; i < nodeNames.length; i++) nodeNames[i] = strings[i]; } // Store the names of the parameters for(i = affector.getNumNodes(); i < num_params; i++) { int pos = Affector.getParamPos(strings[i]); itsParameters.addElement(new Integer(pos)); } if(have_position) // Get the position info { intersectX = (int)Float.valueOf(strings[num_params]).floatValue(); intersectY = (int)Float.valueOf(strings[num_params+1]).floatValue(); } // Tell the affector about its parameters // Note - we use num_params - affector.getNumNodes() rather than affector.getNUmParameters // because meta-affectors won't always know how many parameters they have - but for other affectors // we've already checked above if we have right number so this should be fine. // If the position is stored as the last two parameters, we subtracted those out above int [] param_nums = new int[num_params - affector.getNumNodes()]; for(i = 0; i < num_params - affector.getNumNodes(); i++) param_nums[i] = ((Integer)itsParameters.elementAt(i)).intValue(); affector.setParameterNumbers(param_nums); } private Affector createAffector(String name) throws Exception { Class c = Class.forName("affectors." + name); Affector aff = (Affector)c.newInstance(); return aff; } private void readInteractionSet(BetterTokenizer tokenizer, String end_text) throws Exception { String affector_name = GeneralInput.findNextIDToken(tokenizer); while(!affector_name.equals(end_text)) { // read in affectors for this node AffectorTemplate subaffector = new AffectorTemplate(affectorNode); subaffector.readSetup(tokenizer, affector_name); ((EnhancerRegionAff)affector).addAffector(subaffector.getAffector()); enhancerTemplates.addElement(subaffector); affector_name = GeneralInput.findNextIDToken(tokenizer); } } public void paint(Graphics g0, int cur_node_num, Network network, AffectorTemplate hilight_aff) { Graphics2D g = (Graphics2D)g0; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); try { if(intersectX == -1000) calculateDefaultIntersect(network); NodeTemplate node = network.getNode(affectorNode); if(this == hilight_aff) g.setColor(Color.blue); else if(node.getNodeNum() == cur_node_num) g.setColor(Color.green); else g.setColor(Color.gray); if(affector instanceof EnhancerRegionAff) { Enumeration enum = enhancerTemplates.elements(); while(enum.hasMoreElements()) { AffectorTemplate template2 = (AffectorTemplate)enum.nextElement(); template2.paint(g, cur_node_num, network, hilight_aff); } return; // For now, don't draw enhancer itself } else if(affector instanceof DecayAff) { // Unlike other affectors, we just always draw the decay affector in fixed position relative to node // Set intersectX position here so its always below node - this is only convenient place to do that intersectX = node.getXPos(); intersectY = node.getYPos() + 25; // Draw the line to the node drawPath(g, node, intersectX, intersectY, false); // Draw a grounding symbol down from the intersection point g.drawLine(intersectX, intersectY, intersectX, intersectY + 5); g.drawLine(intersectX - 8, intersectY, intersectX + 8, intersectY); g.drawLine(intersectX - 5, intersectY + 2, intersectX + 5, intersectY + 2); g.drawLine(intersectX - 3, intersectY + 4, intersectX + 3, intersectY + 4); g.setColor(Color.black); return; // No circle } else if(nodeNames == null) { // Draw the line to the node drawPath(g, node, intersectX, intersectY, false); // Draw a box at the end g.fillRect(intersectX - 3, intersectY - 3, 6, 6); g.setColor(Color.black); return; // No circle } else { paintOutgoingLines(g, network, intersectX, intersectY, node, cur_node_num, hilight_aff); drawPath(g, node, intersectX, intersectY, true); } // Draw circle at intersection if(intersectX != node.getXPos() || intersectY != node.getYPos()) g.fillOval(intersectX - 5, intersectY - 5, 10, 10); g.setColor(Color.black); } catch(Exception e) { System.out.println("Error plotting affector " + getAffectorName() + ": " + e.toString()); } } protected void paintOutgoingLines(Graphics g0, Network network, int intersect_x, int intersect_y, NodeTemplate node, int cur_node_num, AffectorTemplate hilight_aff) throws Exception { Graphics2D g = (Graphics2D)g0; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); if(affector instanceof EnhancerRegionAff) { Enumeration enum = enhancerTemplates.elements(); while(enum.hasMoreElements()) { AffectorTemplate template2 = (AffectorTemplate)enum.nextElement(); template2.paintOutgoingLines(g, network, intersectX, intersectY, node, cur_node_num, hilight_aff); } } else if(nodeNames == null) return; // Don't know what to do here. Probably will never happen else { for(int i = 0; i < nodeNames.length; i++) { NodeTemplate other_node = network.getNode(nodeNames[i]); if(other_node.getNodeNum() == cur_node_num) g.setColor(Color.red); drawPath(g, other_node, intersect_x, intersect_y, false); if(other_node.getNodeNum() == cur_node_num) { if(this == hilight_aff) g.setColor(Color.blue); else if(node.getNodeNum() == cur_node_num) g.setColor(Color.green); else g.setColor(Color.gray); } } } } private void drawPath(Graphics g0, NodeTemplate node, int mid_x, int mid_y, boolean draw_arrow) { Graphics2D g = (Graphics2D)g0; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); int x_diff = mid_x - node.getXPos(); int y_diff = mid_y - node.getYPos(); int x, y; if(x_diff == 0) { x = 0; if(y_diff < 0) y = -20; else if(y_diff > 0) y = 20; else y = 0; } else { float slope = (float)y_diff / x_diff; float x_dist = (float)Math.sqrt(400 / (1 + slope * slope)); if(x_diff < 0) x_dist = -x_dist; x = Math.round(x_dist); y = Math.round(slope * x_dist); } g.drawLine(node.getXPos() + x, node.getYPos() + y, mid_x, mid_y); if(draw_arrow) { drawArrowHead(g, node.getXPos() + x, node.getYPos() + y, node.getXPos() + x - mid_x, mid_y - node.getYPos() + y); } } private void drawArrowHead(Graphics g0, int x, int y, float vecx, float vecy) { Graphics2D g = (Graphics2D)g0; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); // Draw an arrow head on the end. I'm drawing arrow head by taking a vector // going backwards from this vector (i.e. - starts at x2,y2) and then rotating // it 30 and -30 degrees, and drawing 4 pixel stubs in those directions. // This means multiplying by matrix [cos, -sin][sin, cos]. float arrowx = cosAngle * -vecx + -sinAngle * -vecy; float arrowy = sinAngle * -vecx + cosAngle * -vecy; float dist = (float)Math.sqrt(arrowx * arrowx + arrowy * arrowy); if(dist > 0) { arrowx = arrowx * arrowLen / dist; arrowy = arrowy * arrowLen / dist; Polygon poly = new Polygon(); poly.addPoint(x, y); poly.addPoint(Math.round(x + arrowx), Math.round(y - arrowy)); arrowx = cosAngle * -vecx + sinAngle * -vecy; arrowy = -sinAngle * -vecx + cosAngle * -vecy; arrowx = arrowx * arrowLen / dist; arrowy = arrowy * arrowLen / dist; poly.addPoint(Math.round(x + arrowx), Math.round(y - arrowy)); poly.addPoint(x, y); g.fillPolygon(poly); } } public void calculateDefaultIntersect(Network network) { NodeTemplate node = null, other_node = null; try { if(affector instanceof EnhancerRegionAff) { // Figure out the first other node node = network.getNode(affectorNode); if(nodeNames != null && nodeNames.length > 0) other_node = network.getNode(nodeNames[0]); else { boolean done = false; Enumeration enum = enhancerTemplates.elements(); while(!done && enum.hasMoreElements()) { AffectorTemplate template = (AffectorTemplate)enum.nextElement(); if(template.nodeNames != null && template.nodeNames.length > 0) { done = true; other_node = network.getNode(template.nodeNames[0]); } } if(! done) // Don't plot { intersectX = network.getNode(affectorNode).getXPos(); intersectY = network.getNode(affectorNode).getYPos(); return; } } } else if(affector instanceof DecayAff) { intersectX = network.getNode(affectorNode).getXPos(); intersectY = network.getNode(affectorNode).getYPos() + 25; return; } else if(nodeNames == null || nodeNames.length == 0 || nodeNames.length == 1 && nodeNames[0].equals(affectorNode)) { // This could be a function affector? Just draw above intersectX = network.getNode(affectorNode).getXPos(); intersectY = network.getNode(affectorNode).getYPos() - 25; return; } else { // Use the first node to decide where midpoint will be node = network.getNode(affectorNode); other_node = network.getNode(nodeNames[0]); if(nodeNames[0].equals(affectorNode) && nodeNames.length > 1) other_node = network.getNode(nodeNames[1]); } int x_diff = node.getXPos() - other_node.getXPos(); int y_diff = node.getYPos() - other_node.getYPos(); int x, y; if(x_diff == 0) { x = 0; if(y_diff < 0) y = -20; else if(y_diff > 0) y = 20; else y = 0; } else { float slope = (float)y_diff / x_diff; float x_dist = (float)Math.sqrt(400 / (1 + slope * slope)); if(x_diff < 0) x_dist = -x_dist; x = Math.round(x_dist); y = Math.round(slope * x_dist); } // Move the intersection point slightly normal to the connecting line so lines going both // ways between nodes are separated. int x_offset = -y_diff; int y_offset = x_diff; float dist = (float)Math.sqrt(x_offset * x_offset + y_offset * y_offset); x_offset = (int)(x_offset * 25 / dist); y_offset = (int)(y_offset * 25 / dist); intersectX = other_node.getXPos() + (int)Math.round(x_diff * 0.6) + x_offset; intersectY = other_node.getYPos() + (int)Math.round(y_diff * 0.6) + y_offset; } catch(Exception e) { System.out.println("Error calculating default affector position " + getAffectorName() + ": " + e.toString()); } } public int getIntersectX() { return intersectX; } public void setIntersectX(int new_x) { intersectX = new_x; } public int getIntersectY() { return intersectY; } public void setIntersectY(int new_y) { intersectY = new_y; } public void moveIntersectPos(int move_x, int move_y) { setIntersectX(getIntersectX() + move_x); setIntersectY(getIntersectY() + move_y); Enumeration enum = enhancerTemplates.elements(); while(enum.hasMoreElements()) { AffectorTemplate template = (AffectorTemplate)enum.nextElement(); template.moveIntersectPos(move_x, move_y); } } public AffectorTemplate isPositionInside(int x, int y, int radius) { // Check subaffectors first, if there are any Enumeration enum = enhancerTemplates.elements(); while(enum.hasMoreElements()) { AffectorTemplate template = (AffectorTemplate)enum.nextElement(); AffectorTemplate ret_template = template.isPositionInside(x, y, radius); if(ret_template != null) return ret_template; } // If this is a meta-affector, don't check this affector directly - will want to change this eventually if(enhancerTemplates.size() > 0) return null; // Finally check this affector if(x > intersectX - radius && x < intersectX + radius && y > intersectY - radius && y < intersectY + radius) return this; return null; } public String getAffectorName() { String aff_name = affector.getClass().getName(); // aff_name = aff_name.substring(aff_name.indexOf('.') + 1, aff_name.length() - 3); return aff_name; } public Affector getAffector() { return affector; } /** This function is here so that viewers can examine an affector. It should not be used by any of the modeling classes themselves. */ public String [] getNodeNames() { return nodeNames; } /** Returns the number of parameters that this affector has. */ public int getNumParameters() { return findAllAffectorParameters().size(); } /** This function returns an enumeration of a vector containing the numbers of each of the affectors parameters (as Integers). This is here so that viewers can examine and change the values of an Affector's parameters. It should not be used by any of the modeling classes. */ public Enumeration getAffectorParameters() { return findAllAffectorParameters().elements(); } /** This function is used to find all the affector parameters from the subaffectors. It puts them all in a Vector. It avoids putting duplicates of the same parameter into the Vector. */ private Vector findAllAffectorParameters() { // If its not a meta affector, just return list of parameters if(enhancerTemplates.size() == 0) return itsParameters; Vector params = new Vector(); Enumeration enum = itsParameters.elements(); while(enum.hasMoreElements()) { Integer num = (Integer)enum.nextElement(); params.addElement(num); } enum = enhancerTemplates.elements(); while(enum.hasMoreElements()) { Enumeration enum2 = ((AffectorTemplate)enum.nextElement()).getAffectorParameters(); while(enum2.hasMoreElements()) { Integer num = (Integer)enum2.nextElement(); // Search through the vector for a number of this type already Enumeration enum3 = params.elements(); boolean found = false; while(enum3.hasMoreElements() && !found) { if(((Integer)enum3.nextElement()).intValue() == num.intValue()) found = true; } if(!found) params.addElement(num); } } return params; } public void toString(PrintWriter pw, String indent) { // Ignores indent here, but used in subclasses int i; String str; // Print out the nodes if(nodeNames != null) { for(i = 0; i < nodeNames.length; i++) pw.print("\t" + nodeNames[i]); } // Print out the parameters for(Enumeration enum = itsParameters.elements(); enum.hasMoreElements(); ) { Integer num = (Integer)enum.nextElement(); pw.print("\t" + Affector.getParamName(num.intValue())); } // Print the drawing position if(intersectX != -1000 && intersectY != -1000) pw.print("\t" + intersectX + "\t" + intersectY); pw.println(); Enumeration enum = enhancerTemplates.elements(); while(enum.hasMoreElements()) { AffectorTemplate enhancer = (AffectorTemplate)enum.nextElement(); str = enhancer.getAffector().getClass().getName(); pw.print(indent + "\t&" + str.substring(str.indexOf(".") + 1)); enhancer.toString(pw, indent + "\t"); } if(enhancerTemplates.size() > 0) { str = getAffector().getClass().getName(); pw.println(indent + "&end" + str.substring(str.indexOf(".") + 1)); } } }