/* Copyright (c) 2010, Carl Burch. License information is located in the
* com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */
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.ActionListener;
import java.awt.event.ActionEvent;
import java.util.Map;
import javax.swing.JPopupMenu;
import javax.swing.JMenuItem;
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 String get() {
return source.getName();
}
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 void actionPerformed(ActionEvent e) {
CircuitState superState = proj.getCircuitState();
if (superState == null) return;
CircuitState subState = getSubstate(superState, instance);
if (subState == null) return;
proj.setCircuitState(subState);
}
}
private Circuit source;
public SubcircuitFactory(Circuit source) {
super("", null);
this.source = source;
setFacingAttribute(StdAttr.FACING);
setDefaultToolTip(new CircuitFeature(null));
setInstancePoker(SubcircuitPoker.class);
}
public Circuit getSubcircuit() {
return source;
}
@Override
public String getName() {
return source.getName();
}
@Override
public StringGetter getDisplayGetter() {
return StringUtil.constantGetter(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);
}
@Override
public AttributeSet createAttributeSet() {
return new CircuitAttributes(source);
}
//
// 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
}
@Override
public void instanceAttributeChanged(Instance instance, Attribute<?> attr) {
if (attr == StdAttr.FACING) {
computePorts(instance);
} else if (attr == CircuitAttributes.LABEL_LOCATION_ATTR) {
configureLabel(instance);
}
}
@Override
public Object getInstanceFeature(Instance instance, Object key) {
if (key == MenuExtender.class) return new CircuitFeature(instance);
return super.getInstanceFeature(instance, key);
}
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);
}
//
// propagation-oriented methods
//
public CircuitState getSubstate(CircuitState superState, Instance instance) {
return getSubstate(createInstanceState(superState, instance));
}
public CircuitState getSubstate(CircuitState superState, Component comp) {
return getSubstate(createInstanceState(superState, comp));
}
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 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.getPort(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.getPort(0);
superState.setPort(i, val, 1);
}
}
}
//
// 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();
}
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();
}
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();
}
}
/* TODO
public String getToolTip(ComponentUserEvent e) {
return StringUtil.format(Strings.get("subcircuitCircuitTip"), source.getDisplayName());
} */
}