/******************************************************************************* * This file is part of logisim-evolution. * * logisim-evolution is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * logisim-evolution 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 logisim-evolution. If not, see <http://www.gnu.org/licenses/>. * * Original code by Carl Burch (http://www.cburch.com), 2011. * Subsequent modifications by : * + Haute École Spécialisée Bernoise * http://www.bfh.ch * + Haute École du paysage, d'ingénierie et d'architecture de Genève * http://hepia.hesge.ch/ * + Haute École d'Ingénierie et de Gestion du Canton de Vaud * http://www.heig-vd.ch/ * The project is currently maintained by : * + REDS Institute - HEIG-VD * Yverdon-les-Bains, Switzerland * http://reds.heig-vd.ch *******************************************************************************/ package com.cburch.logisim.circuit; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Composite; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import com.bfh.logisim.hdlgenerator.CircuitHDLGeneratorFactory; import com.cburch.draw.shapes.DrawAttr; import com.cburch.draw.shapes.Text; import com.cburch.logisim.circuit.appear.CircuitAppearance; import com.cburch.logisim.comp.Component; import com.cburch.logisim.data.Attribute; import com.cburch.logisim.data.AttributeSet; import com.cburch.logisim.data.BitWidth; import com.cburch.logisim.data.Bounds; import com.cburch.logisim.data.Direction; import com.cburch.logisim.data.Location; import com.cburch.logisim.data.Value; import com.cburch.logisim.instance.Instance; import com.cburch.logisim.instance.InstanceFactory; import com.cburch.logisim.instance.InstancePainter; import com.cburch.logisim.instance.InstanceState; import com.cburch.logisim.instance.Port; import com.cburch.logisim.instance.StdAttr; import com.cburch.logisim.proj.Project; import com.cburch.logisim.std.wiring.Pin; import com.cburch.logisim.tools.MenuExtender; import com.cburch.logisim.util.GraphicsUtil; import com.cburch.logisim.util.StringGetter; import com.cburch.logisim.util.StringUtil; public class SubcircuitFactory extends InstanceFactory { private class CircuitFeature implements StringGetter, MenuExtender, ActionListener { private Instance instance; private Project proj; public CircuitFeature(Instance instance) { this.instance = instance; } public void actionPerformed(ActionEvent e) { CircuitState superState = proj.getCircuitState(); if (superState == null) return; CircuitState subState = getSubstate(superState, instance); if (subState == null) return; proj.setCircuitState(subState); } public void configureMenu(JPopupMenu menu, Project proj) { this.proj = proj; String name = instance.getFactory().getDisplayName(); String text = Strings.get("subcircuitViewItem", name); JMenuItem item = new JMenuItem(text); item.addActionListener(this); menu.add(item); } public String toString() { return source.getName(); } } private Circuit source; public SubcircuitFactory(Circuit source) { super("", null); this.source = source; setFacingAttribute(StdAttr.FACING); setDefaultToolTip(new CircuitFeature(null)); setInstancePoker(SubcircuitPoker.class); } void computePorts(Instance instance) { Direction facing = instance.getAttributeValue(StdAttr.FACING); Map<Location, Instance> portLocs = source.getAppearance() .getPortOffsets(facing); Port[] ports = new Port[portLocs.size()]; Instance[] pins = new Instance[portLocs.size()]; int i = -1; for (Map.Entry<Location, Instance> portLoc : portLocs.entrySet()) { i++; Location loc = portLoc.getKey(); Instance pin = portLoc.getValue(); String type = Pin.FACTORY.isInputPin(pin) ? Port.INPUT : Port.OUTPUT; BitWidth width = pin.getAttributeValue(StdAttr.WIDTH); ports[i] = new Port(loc.getX(), loc.getY(), type, width); pins[i] = pin; String label = pin.getAttributeValue(StdAttr.LABEL); if (label != null && label.length() > 0) { ports[i].setToolTip(StringUtil.constantGetter(label)); } } CircuitAttributes attrs = (CircuitAttributes) instance .getAttributeSet(); attrs.setPinInstances(pins); instance.setPorts(ports); instance.recomputeBounds(); configureLabel(instance); // since this affects the circuit's bounds } private void configureLabel(Instance instance) { Bounds bds = instance.getBounds(); Direction loc = instance .getAttributeValue(CircuitAttributes.LABEL_LOCATION_ATTR); int x = bds.getX() + bds.getWidth() / 2; int y = bds.getY() + bds.getHeight() / 2; int ha = GraphicsUtil.H_CENTER; int va = GraphicsUtil.V_CENTER; if (loc == Direction.EAST) { x = bds.getX() + bds.getWidth() + 2; ha = GraphicsUtil.H_LEFT; } else if (loc == Direction.WEST) { x = bds.getX() - 2; ha = GraphicsUtil.H_RIGHT; } else if (loc == Direction.SOUTH) { y = bds.getY() + bds.getHeight() + 2; va = GraphicsUtil.V_TOP; } else { y = bds.getY() - 2; va = GraphicsUtil.V_BASELINE; } instance.setTextField(StdAttr.LABEL, StdAttr.LABEL_FONT, x, y, ha, va); } // // methods for configuring instances // @Override public void configureNewInstance(Instance instance) { CircuitAttributes attrs = (CircuitAttributes) instance .getAttributeSet(); attrs.setSubcircuit(instance); instance.addAttributeListener(); computePorts(instance); // configureLabel(instance); already done in computePorts } /** * Code taken from Cornell's version of Logisim: * http://www.cs.cornell.edu/courses/cs3410/2015sp/ */ @Override public boolean contains(Location loc, AttributeSet attrs) { if (super.contains(loc, attrs)) { Direction facing = attrs.getValue(StdAttr.FACING); Direction defaultFacing = source.getAppearance().getFacing(); Location query; if (facing.equals(defaultFacing)) { query = loc; } else { query = loc.rotate(facing, defaultFacing, 0, 0); } return source.getAppearance().contains(query); } else { return false; } } @Override public AttributeSet createAttributeSet() { return new CircuitAttributes(source); } /** * This function is in charge of displaying a subcircuit, * and to take care of rotating the strings accordingly. * @param painter the painter gotten from paint event * @param bds border limits * @param facing Direction in case of rotation * @param defaultFacing Default direction when element was * created */ private void drawCircuitName(InstancePainter painter, Bounds bds, Direction facing, Direction defaultFacing) { String label = source.getName(); Map<Direction, List<Instance>> edge; int x; int y; int y_label = 0; /* This is made to count the pins on each side */ edge = new HashMap<Direction, List<Instance>>(); edge.put(Direction.EAST, new ArrayList<Instance>()); edge.put(Direction.WEST, new ArrayList<Instance>()); Collection<Instance> pins = source.getAppearance().GetCircuitPin().getPins(); if (!pins.isEmpty()) { for (Instance pin : pins) { Direction pinEdge; if (pin.getAttributeValue(Pin.ATTR_TYPE)) { pinEdge=Direction.EAST; } else { pinEdge=Direction.WEST; } List<Instance> e = edge.get(pinEdge); e.add(pin); } } int maxNumber = Math.max(edge.get(Direction.EAST).size(), edge.get(Direction.WEST).size()); maxNumber--; if (label != null && !label.equals("")) { Direction up = Direction.NORTH; Font font = DrawAttr.DEFAULT_NAME_FONT; Graphics g = painter.getGraphics().create(); FontMetrics m = g.getFontMetrics(font); double angle; if (facing == Direction.WEST) { angle = Math.PI / 2 - (up.toRadians() - defaultFacing.toRadians()); x = bds.getX() + bds.getWidth() / 2; y = bds.getY() + m.getAscent() / 2 ; y_label = y ; g.fillRect(bds.getX() + CircuitAppearance.PINLENGTH , bds.getY(), bds.getWidth() - 2 * CircuitAppearance.PINLENGTH, m.getHeight() ); } else { angle = Math.PI / 2 - (up.toRadians() - defaultFacing.toRadians()) - facing.toRadians(); if ( facing == Direction.NORTH) { x = bds.getX() + bds.getWidth() / 2 + m.getAscent(); y = bds.getY() + bds.getHeight() / 2; /* Get position regarding the box itself meaning that when rotated the x\y axis follows */ y_label = y + maxNumber * m.getAscent(); g.fillRect(bds.getX() + bds.getWidth() - m.getHeight() - 1, bds.getY() + CircuitAppearance.PINLENGTH, m.getHeight(), bds.getHeight() - 2 * CircuitAppearance.PINLENGTH ); } else if ( facing == Direction.EAST) { x = bds.getX() + bds.getWidth() / 2; y = bds.getY() + bds.getHeight() - m.getAscent(); /* Get position regarding the box itself meaning that when rotated the x\y axis follows */ y_label = y; g.fillRect(bds.getX() + CircuitAppearance.PINLENGTH, bds.getY() + bds.getHeight() - m.getHeight() , bds.getWidth() - 2 * CircuitAppearance.PINLENGTH, m.getHeight() ); } else { x = bds.getX() + bds.getWidth() / 2 - m.getAscent(); y = bds.getY() + bds.getHeight() /2; y_label = y + maxNumber * m.getAscent(); g.fillRect(bds.getX() + 1 , bds.getY() + CircuitAppearance.PINLENGTH, m.getHeight(), bds.getHeight() - 2 * CircuitAppearance.PINLENGTH ); } } if (g instanceof Graphics2D && Math.abs(angle) > 0.01) { Graphics2D g2 = (Graphics2D) g; g2.rotate(angle, x, y); } g.setFont(font); GraphicsUtil.drawCenteredColoredText(g, label, Color.WHITE, Color.BLACK, x, y_label); g.dispose(); } } /** * This function will be in charge of placing the name of the pin and rotate them * accordingly avoid the names being upside down * @param painter * @param edge * @param x * @param y * @param dx * @param dy * @param LeftSide * @param angle */ private static void placePins(InstancePainter painter, Map<Direction, List<Instance>> edge, int x, int y, int width, int height, double angle, Direction facing) { /* Here is checking which side of the circuit has * the most pins */ int ldx = 0; int ldy = 0; int maxVert = Math.max(edge.get(Direction.EAST).size(), edge.get(Direction.WEST).size()); List<Instance> pins_west = edge.get(Direction.WEST); List<Instance> pins_east = edge.get(Direction.EAST); Font pins_font = DrawAttr.DEFAULT_FIXED_PICH_FONT; for (int i = 0; i < maxVert ; i++) { /* Draw East side pins */ if (pins_east.size() > i) { Instance pin_east = pins_east.get(i); if (pin_east.getAttributeSet().containsAttribute(StdAttr.LABEL)) { Graphics g1 = painter.getGraphics().create(); FontMetrics m = g1.getFontMetrics(pins_font); if (Direction.WEST == facing) { ldx = 20; ldy = - (i* (m.getHeight() + 5) - 2); } else { ldx = - m.stringWidth(pin_east.getAttributeValue(StdAttr.LABEL)) -20; ldy = (i* (m.getHeight() + 5) + 5); } ((Graphics2D) g1).rotate(angle); ((Graphics2D) g1).setFont(pins_font); ((Graphics2D) g1).drawString( pin_east.getAttributeValue(StdAttr.LABEL), ldx, ldy); g1.dispose(); } } /* Draw West side pins */ if (pins_west.size() > i) { Instance pin_west = pins_west.get(i); if (pin_west.getAttributeSet().containsAttribute(StdAttr.LABEL)) { Graphics g1 = painter.getGraphics().create(); FontMetrics m = g1.getFontMetrics(pins_font); ((Graphics2D) g1).setFont(pins_font); ((Graphics2D) g1).rotate(angle); if (Direction.SOUTH == facing) { ldx = -height + 20; ldy = (i* (m.getHeight() + 5) + 5); } else if (Direction.WEST == facing) { ldx = width - m.stringWidth(pin_west.getAttributeValue(StdAttr.LABEL)) -20; ldy = - (i* (m.getHeight() + 5) - 2); System.out.println("Size string is : " + ldx + " x " + ldy ); } else if (Direction.EAST == facing) { ldx = -width + 20; ldy = (i* (m.getHeight() + 5) + 5); } else if (Direction.NORTH == facing) { ldx = -height + 20; ldy = (i* (m.getHeight() + 5) + 5); } ((Graphics2D) g1).drawString(pin_west.getAttributeValue(StdAttr.LABEL), ldx, ldy); g1.dispose(); } } } } private void drawPinsName(InstancePainter painter, Bounds bds, Direction facing, Direction defaultFacing) { Map<Direction, List<Instance>> edge; Collection<Instance> pins = source.getAppearance().GetCircuitPin().getPins(); edge = new HashMap<Direction, List<Instance>>(); edge.put(Direction.EAST, new ArrayList<Instance>()); edge.put(Direction.WEST, new ArrayList<Instance>()); int MaxLeftLabelLength = 0; int MaxRightLabelLength = 0; double angle; for (Instance pin : pins) { Direction pinEdge; Text label = new Text(0,0,pin.getAttributeValue(StdAttr.LABEL)); int LabelWidth = label.getText().length()*DrawAttr.FixedFontCharWidth; if (pin.getAttributeValue(Pin.ATTR_TYPE)) { pinEdge=Direction.EAST; if (LabelWidth>MaxRightLabelLength) MaxRightLabelLength = LabelWidth; } else { pinEdge=Direction.WEST; if (LabelWidth>MaxLeftLabelLength) MaxLeftLabelLength = LabelWidth; } List<Instance> e = edge.get(pinEdge); e.add(pin); } for (Map.Entry<Direction, List<Instance>> entry : edge.entrySet()) { source.getAppearance().sortPinsList(entry.getValue(), entry.getKey()); } /* Here we manage to have always the beginning of the pin label * close to the pin and avoid the label being upside down. */ angle = facing.toRadians(); angle = Math.toRadians(-Math.toDegrees(angle) % Math.toDegrees(Math.PI) ); placePins(painter, edge, bds.getX(), bds.getY(), bds.getWidth(), bds.getHeight(), angle, facing); } private void drawCircuitLabel(InstancePainter painter, Bounds bds, Direction facing, Direction defaultFacing) { AttributeSet staticAttrs = source.getStaticAttributes(); String label = staticAttrs .getValue(CircuitAttributes.CIRCUIT_LABEL_ATTR); if (label != null && !label.equals("")) { Direction up = staticAttrs .getValue(CircuitAttributes.CIRCUIT_LABEL_FACING_ATTR); Font font = staticAttrs .getValue(CircuitAttributes.CIRCUIT_LABEL_FONT_ATTR); int back = label.indexOf('\\'); int lines = 1; boolean backs = false; while (back >= 0 && back <= label.length() - 2) { char c = label.charAt(back + 1); if (c == 'n') lines++; else if (c == '\\') backs = true; back = label.indexOf('\\', back + 2); } int x = bds.getX() + bds.getWidth() / 2; int y = bds.getY() + bds.getHeight() / 2; Graphics g = painter.getGraphics().create(); double angle = Math.PI / 2 - (up.toRadians() - defaultFacing.toRadians()) - facing.toRadians(); if (g instanceof Graphics2D && Math.abs(angle) > 0.01) { Graphics2D g2 = (Graphics2D) g; g2.rotate(angle, x, y); } g.setFont(font); if (lines == 1 && !backs) { GraphicsUtil.drawCenteredText(g, label, x, y); } else { FontMetrics fm = g.getFontMetrics(); int height = fm.getHeight(); y = y - (height * lines - fm.getLeading()) / 2 + fm.getAscent(); back = label.indexOf('\\'); while (back >= 0 && back <= label.length() - 2) { char c = label.charAt(back + 1); if (c == 'n') { String line = label.substring(0, back); GraphicsUtil.drawText(g, line, x, y, GraphicsUtil.H_CENTER, GraphicsUtil.V_BASELINE); y += height; label = label.substring(back + 2); back = label.indexOf('\\'); } else if (c == '\\') { label = label.substring(0, back) + label.substring(back + 1); back = label.indexOf('\\', back + 1); } else { back = label.indexOf('\\', back + 2); } } GraphicsUtil.drawText(g, label, x, y, GraphicsUtil.H_CENTER, GraphicsUtil.V_BASELINE); } g.dispose(); } } @Override public StringGetter getDisplayGetter() { return StringUtil.constantGetter(source.getName()); } @Override public Object getInstanceFeature(Instance instance, Object key) { if (key == MenuExtender.class) return new CircuitFeature(instance); return super.getInstanceFeature(instance, key); } @Override public String getName() { return source.getName(); } @Override public Bounds getOffsetBounds(AttributeSet attrs) { Direction facing = attrs.getValue(StdAttr.FACING); Direction defaultFacing = source.getAppearance().getFacing(); Bounds bds = source.getAppearance().getOffsetBounds(); return bds.rotate(defaultFacing, facing, 0, 0); } public Circuit getSubcircuit() { return source; } public void setSubcircuit(Circuit sub) { source = sub; } public CircuitState getSubstate(CircuitState superState, Component comp) { return getSubstate(createInstanceState(superState, comp)); } // // propagation-oriented methods // public CircuitState getSubstate(CircuitState superState, Instance instance) { return getSubstate(createInstanceState(superState, instance)); } private CircuitState getSubstate(InstanceState instanceState) { CircuitState subState = (CircuitState) instanceState.getData(); if (subState == null) { subState = new CircuitState(instanceState.getProject(), source); instanceState.setData(subState); instanceState.fireInvalidated(); } return subState; } @Override public boolean HDLSupportedComponent(String HDLIdentifier, AttributeSet attrs) { if (MyHDLGenerator == null) MyHDLGenerator = new CircuitHDLGeneratorFactory(this.source); return MyHDLGenerator.HDLTargetSupported(HDLIdentifier, attrs); } @Override public void instanceAttributeChanged(Instance instance, Attribute<?> attr) { if (attr == StdAttr.FACING) { computePorts(instance); } else if (attr == CircuitAttributes.LABEL_LOCATION_ATTR) { configureLabel(instance); } } private void paintBase(InstancePainter painter, Graphics g) { CircuitAttributes attrs = (CircuitAttributes) painter.getAttributeSet(); Direction facing = attrs.getFacing(); Direction defaultFacing = source.getAppearance().getFacing(); Location loc = painter.getLocation(); g.translate(loc.getX(), loc.getY()); source.getAppearance().paintSubcircuit(g, facing); drawCircuitLabel(painter, getOffsetBounds(attrs), facing, defaultFacing); g.translate(-loc.getX(), -loc.getY()); painter.drawLabel(); } // // user interface features // @Override public void paintGhost(InstancePainter painter) { Graphics g = painter.getGraphics(); Color fg = g.getColor(); int v = fg.getRed() + fg.getGreen() + fg.getBlue(); Composite oldComposite = null; if (g instanceof Graphics2D && v > 50) { oldComposite = ((Graphics2D) g).getComposite(); Composite c = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f); ((Graphics2D) g).setComposite(c); } paintBase(painter, g); if (oldComposite != null) { ((Graphics2D) g).setComposite(oldComposite); } } @Override public void paintInstance(InstancePainter painter) { paintBase(painter, painter.getGraphics()); painter.drawPorts(); } @Override public void propagate(InstanceState superState) { CircuitState subState = getSubstate(superState); CircuitAttributes attrs = (CircuitAttributes) superState .getAttributeSet(); Instance[] pins = attrs.getPinInstances(); for (int i = 0; i < pins.length; i++) { Instance pin = pins[i]; InstanceState pinState = subState.getInstanceState(pin); if (Pin.FACTORY.isInputPin(pin)) { Value newVal = superState.getPortValue(i); Value oldVal = Pin.FACTORY.getValue(pinState); if (!newVal.equals(oldVal)) { Pin.FACTORY.setValue(pinState, newVal); Pin.FACTORY.propagate(pinState); } } else { // it is output-only Value val = pinState.getPortValue(0); superState.setPort(i, val, 1); } } } @Override public boolean RequiresNonZeroLabel() { return true; } /* * TODO public String getToolTip(ComponentUserEvent e) { return * StringUtil.format(Strings.get("subcircuitCircuitTip"), * source.getDisplayName()); } */ }