/* 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.appear;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import com.cburch.draw.model.CanvasModelEvent;
import com.cburch.draw.model.CanvasModelListener;
import com.cburch.draw.model.CanvasObject;
import com.cburch.draw.model.Drawing;
import com.cburch.logisim.circuit.Circuit;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Direction;
import com.cburch.logisim.data.Location;
import com.cburch.logisim.instance.Instance;
import com.cburch.logisim.util.EventSourceWeakSupport;
public class CircuitAppearance extends Drawing {
private class MyListener implements CanvasModelListener {
@Override
public void modelChanged(CanvasModelEvent event) {
if (!suppressRecompute) {
setDefaultAppearance(false);
fireCircuitAppearanceChanged(CircuitAppearanceEvent.ALL_TYPES);
}
}
}
private Circuit circuit;
private EventSourceWeakSupport<CircuitAppearanceListener> listeners;
private PortManager portManager;
private CircuitPins circuitPins;
private MyListener myListener;
private boolean isDefault;
private boolean suppressRecompute;
public CircuitAppearance(Circuit circuit) {
this.circuit = circuit;
listeners = new EventSourceWeakSupport<CircuitAppearanceListener>();
portManager = new PortManager(this);
circuitPins = new CircuitPins(portManager);
myListener = new MyListener();
suppressRecompute = false;
addCanvasModelListener(myListener);
setDefaultAppearance(true);
}
public CircuitPins getCircuitPins() {
return circuitPins;
}
public void addCircuitAppearanceListener(CircuitAppearanceListener l) {
listeners.add(l);
}
public void removeCircuitAppearanceListener(CircuitAppearanceListener l) {
listeners.remove(l);
}
void fireCircuitAppearanceChanged(int affected) {
CircuitAppearanceEvent event;
event = new CircuitAppearanceEvent(circuit, affected);
for (CircuitAppearanceListener listener : listeners) {
listener.circuitAppearanceChanged(event);
}
}
void replaceAutomatically(List<AppearancePort> removes,
List<AppearancePort> adds) {
// this should be called only when substituting ports via PortManager
boolean oldSuppress = suppressRecompute;
try {
suppressRecompute = true;
removeObjects(removes);
addObjects(getObjectsFromBottom().size() - 1, adds);
recomputeDefaultAppearance();
} finally {
suppressRecompute = oldSuppress;
}
fireCircuitAppearanceChanged(CircuitAppearanceEvent.ALL_TYPES);
}
public boolean isDefaultAppearance() {
return isDefault;
}
public void setDefaultAppearance(boolean value) {
if (isDefault != value) {
isDefault = value;
if (value) {
recomputeDefaultAppearance();
}
}
}
void recomputePorts() {
if (isDefault) {
recomputeDefaultAppearance();
} else {
fireCircuitAppearanceChanged(CircuitAppearanceEvent.ALL_TYPES);
}
}
private void recomputeDefaultAppearance() {
if (isDefault) {
List<CanvasObject> shapes;
shapes = DefaultAppearance.build(circuitPins.getPins());
setObjectsForce(shapes);
}
}
public Direction getFacing() {
AppearanceAnchor anchor = findAnchor();
if (anchor == null) {
return Direction.EAST;
} else {
return anchor.getFacing();
}
}
public void setObjectsForce(List<? extends CanvasObject> shapesBase) {
// This shouldn't ever be an issue, but just to make doubly sure, we'll
// check that the anchor and all ports are in their proper places.
List<CanvasObject> shapes = new ArrayList<CanvasObject>(shapesBase);
int n = shapes.size();
int ports = 0;
// count ports, move anchor to end
for (int i = n - 1; i >= 0; i--) {
CanvasObject o = shapes.get(i);
if (o instanceof AppearanceAnchor) {
if (i != n - 1) {
shapes.remove(i);
shapes.add(o);
}
} else if (o instanceof AppearancePort) {
ports++;
}
}
// move ports to top
for (int i = (n - ports - 1) - 1; i >= 0; i--) {
CanvasObject o = shapes.get(i);
if (o instanceof AppearancePort) {
shapes.remove(i);
shapes.add(n - ports - 1, o);
i--;
}
}
try {
suppressRecompute = true;
super.removeObjects(new ArrayList<CanvasObject>(getObjectsFromBottom()));
super.addObjects(0, shapes);
} finally {
suppressRecompute = false;
}
fireCircuitAppearanceChanged(CircuitAppearanceEvent.ALL_TYPES);
}
public void paintSubcircuit(Graphics g, Direction facing) {
Direction defaultFacing = getFacing();
double rotate = 0.0;
if (facing != defaultFacing && g instanceof Graphics2D) {
rotate = defaultFacing.toRadians() - facing.toRadians();
((Graphics2D) g).rotate(rotate);
}
Location offset = findAnchorLocation();
g.translate(-offset.getX(), -offset.getY());
for (CanvasObject shape : getObjectsFromBottom()) {
if (!(shape instanceof AppearanceElement)) {
Graphics dup = g.create();
shape.paint(dup, null);
dup.dispose();
}
}
g.translate(offset.getX(), offset.getY());
if (rotate != 0.0) {
((Graphics2D) g).rotate(-rotate);
}
}
private Location findAnchorLocation() {
AppearanceAnchor anchor = findAnchor();
if (anchor == null) {
return Location.create(100, 100);
} else {
return anchor.getLocation();
}
}
private AppearanceAnchor findAnchor() {
for (CanvasObject shape : getObjectsFromBottom()) {
if (shape instanceof AppearanceAnchor) {
return (AppearanceAnchor) shape;
}
}
return null;
}
public Bounds getOffsetBounds() {
return getBounds(true);
}
public Bounds getAbsoluteBounds() {
return getBounds(false);
}
private Bounds getBounds(boolean relativeToAnchor) {
Bounds ret = null;
Location offset = null;
for (CanvasObject o : getObjectsFromBottom()) {
if (o instanceof AppearanceElement) {
Location loc = ((AppearanceElement) o).getLocation();
if (o instanceof AppearanceAnchor) {
offset = loc;
}
if (ret == null) {
ret = Bounds.create(loc);
} else {
ret = ret.add(loc);
}
} else {
if (ret == null) {
ret = o.getBounds();
} else {
ret = ret.add(o.getBounds());
}
}
}
if (ret == null) {
return Bounds.EMPTY_BOUNDS;
} else if (relativeToAnchor && offset != null) {
return ret.translate(-offset.getX(), -offset.getY());
} else {
return ret;
}
}
public boolean contains(Location loc) {
Location query;
AppearanceAnchor anchor = findAnchor();
if (anchor == null) {
query = loc;
} else {
Location anchorLoc = anchor.getLocation();
query = loc.translate(anchorLoc.getX(), anchorLoc.getY());
}
for (CanvasObject o : getObjectsFromBottom()) {
if (!(o instanceof AppearanceElement) && o.contains(query, true)) {
return true;
}
}
return false;
}
public SortedMap<Location, Instance> getPortOffsets(Direction facing) {
Location anchor = null;
Direction defaultFacing = Direction.EAST;
List<AppearancePort> ports = new ArrayList<AppearancePort>();
for (CanvasObject shape : getObjectsFromBottom()) {
if (shape instanceof AppearancePort) {
ports.add((AppearancePort) shape);
} else if (shape instanceof AppearanceAnchor) {
AppearanceAnchor o = (AppearanceAnchor) shape;
anchor = o.getLocation();
defaultFacing = o.getFacing();
}
}
SortedMap<Location, Instance> ret = new TreeMap<Location, Instance>();
for (AppearancePort port : ports) {
Location loc = port.getLocation();
if (anchor != null) {
loc = loc.translate(-anchor.getX(), -anchor.getY());
}
if (facing != defaultFacing) {
loc = loc.rotate(defaultFacing, facing, 0, 0);
}
ret.put(loc, port.getPin());
}
return ret;
}
@Override
public void addObjects(int index, Collection<? extends CanvasObject> shapes) {
super.addObjects(index, shapes);
checkToFirePortsChanged(shapes);
}
@Override
public void addObjects(Map<? extends CanvasObject, Integer> shapes) {
super.addObjects(shapes);
checkToFirePortsChanged(shapes.keySet());
}
@Override
public void removeObjects(Collection<? extends CanvasObject> shapes) {
super.removeObjects(shapes);
checkToFirePortsChanged(shapes);
}
@Override
public void translateObjects(Collection<? extends CanvasObject> shapes, int dx, int dy) {
super.translateObjects(shapes, dx, dy);
checkToFirePortsChanged(shapes);
}
private void checkToFirePortsChanged(Collection<? extends CanvasObject> shapes) {
if (affectsPorts(shapes)) {
recomputePorts();
}
}
private boolean affectsPorts(Collection<? extends CanvasObject> shapes) {
for (CanvasObject o : shapes) {
if (o instanceof AppearanceElement) {
return true;
}
}
return false;
}
}