/* 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.instance;
import java.awt.Font;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import com.cburch.logisim.circuit.CircuitState;
import com.cburch.logisim.comp.Component;
import com.cburch.logisim.comp.ComponentDrawContext;
import com.cburch.logisim.comp.ComponentEvent;
import com.cburch.logisim.comp.ComponentFactory;
import com.cburch.logisim.comp.ComponentListener;
import com.cburch.logisim.comp.ComponentUserEvent;
import com.cburch.logisim.comp.EndData;
import com.cburch.logisim.data.Attribute;
import com.cburch.logisim.data.AttributeEvent;
import com.cburch.logisim.data.AttributeListener;
import com.cburch.logisim.data.AttributeSet;
import com.cburch.logisim.data.BitWidth;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Location;
import com.cburch.logisim.tools.TextEditable;
import com.cburch.logisim.tools.ToolTipMaker;
import com.cburch.logisim.util.EventSourceWeakSupport;
import com.cburch.logisim.util.StringGetter;
import com.cburch.logisim.util.UnmodifiableList;
class InstanceComponent implements Component, AttributeListener, ToolTipMaker {
private EventSourceWeakSupport<ComponentListener> listeners;
private InstanceFactory factory;
private Instance instance;
private Location loc;
private Bounds bounds;
private List<Port> portList;
private EndData[] endArray;
private List<EndData> endList;
private boolean hasToolTips;
private HashSet<Attribute<BitWidth>> widthAttrs;
private AttributeSet attrs;
private boolean attrListenRequested;
private InstanceTextField textField;
InstanceComponent(InstanceFactory factory, Location loc,
AttributeSet attrs) {
this.listeners = null;
this.factory = factory;
this.instance = new Instance(this);
this.loc = loc;
this.bounds = factory.getOffsetBounds(attrs).translate(loc.getX(), loc.getY());
this.portList = factory.getPorts();
this.endArray = null;
this.hasToolTips = false;
this.attrs = attrs;
this.attrListenRequested = false;
this.textField = null;
computeEnds();
}
private void computeEnds() {
List<Port> ports = portList;
EndData[] esOld = endArray;
int esOldLength = esOld == null ? 0 : esOld.length;
EndData[] es = esOld;
if (es == null || es.length != ports.size()) {
es = new EndData[ports.size()];
if (esOldLength > 0) {
int toCopy = Math.min(esOldLength, es.length);
System.arraycopy(esOld, 0, es, 0, toCopy);
}
}
HashSet<Attribute<BitWidth>> wattrs = null;
boolean toolTipFound = false;
ArrayList<EndData> endsChangedOld = null;
ArrayList<EndData> endsChangedNew = null;
Iterator<Port> pit = ports.iterator();
for (int i = 0; pit.hasNext() || i < esOldLength; i++) {
Port p = pit.hasNext() ? pit.next() : null;
EndData oldEnd = i < esOldLength ? esOld[i] : null;
EndData newEnd = p == null ? null : p.toEnd(loc, attrs);
if (oldEnd == null || !oldEnd.equals(newEnd)) {
if (newEnd != null) es[i] = newEnd;
if (endsChangedOld == null) {
endsChangedOld = new ArrayList<EndData>();
endsChangedNew = new ArrayList<EndData>();
}
endsChangedOld.add(oldEnd);
endsChangedNew.add(newEnd);
}
if (p != null) {
Attribute<BitWidth> attr = p.getWidthAttribute();
if (attr != null) {
if (wattrs == null) {
wattrs = new HashSet<Attribute<BitWidth>>();
}
wattrs.add(attr);
}
if (p.getToolTip() != null) toolTipFound = true;
}
}
if (!attrListenRequested) {
HashSet<Attribute<BitWidth>> oldWattrs = widthAttrs;
if (wattrs == null && oldWattrs != null) {
getAttributeSet().removeAttributeListener(this);
} else if (wattrs != null && oldWattrs == null) {
getAttributeSet().addAttributeListener(this);
}
}
if (es != esOld) {
endArray = es;
endList = new UnmodifiableList<EndData>(es);
}
widthAttrs = wattrs;
hasToolTips = toolTipFound;
if (endsChangedOld != null) {
fireEndsChanged(endsChangedOld, endsChangedNew);
}
}
//
// listening methods
//
public void addComponentListener(ComponentListener l) {
EventSourceWeakSupport<ComponentListener> ls = listeners;
if (ls == null) {
ls = new EventSourceWeakSupport<ComponentListener>();
ls.add(l);
listeners = ls;
} else {
ls.add(l);
}
}
public void removeComponentListener(ComponentListener l) {
if (listeners != null) {
listeners.remove(l);
if (listeners.isEmpty()) listeners = null;
}
}
private void fireEndsChanged(ArrayList<EndData> oldEnds,
ArrayList<EndData> newEnds) {
EventSourceWeakSupport<ComponentListener> ls = listeners;
if (ls != null) {
ComponentEvent e = null;
for (ComponentListener l : ls) {
if (e == null) e = new ComponentEvent(this, oldEnds, newEnds);
l.endChanged(e);
}
}
}
void fireInvalidated() {
EventSourceWeakSupport<ComponentListener> ls = listeners;
if (ls != null) {
ComponentEvent e = null;
for (ComponentListener l : ls) {
if (e == null) e = new ComponentEvent(this);
l.componentInvalidated(e);
}
}
}
//
// basic information methods
//
public ComponentFactory getFactory() {
return factory;
}
public AttributeSet getAttributeSet() {
return attrs;
}
public Object getFeature(Object key) {
Object ret = factory.getInstanceFeature(instance, key);
if (ret != null) {
return ret;
} else if (key == ToolTipMaker.class) {
Object defaultTip = factory.getDefaultToolTip();
if (hasToolTips || defaultTip != null) return this;
} else if (key == TextEditable.class) {
InstanceTextField field = textField;
if (field != null) return field;
}
return null;
}
//
// location/extent methods
//
public Location getLocation() {
return loc;
}
public Bounds getBounds() {
return bounds;
}
public Bounds getBounds(Graphics g) {
Bounds ret = bounds;
InstanceTextField field = textField;
if (field != null) ret = ret.add(field.getBounds(g));
return ret;
}
public boolean contains(Location pt) {
Location translated = pt.translate(-loc.getX(), -loc.getY());
InstanceFactory factory = instance.getFactory();
return factory.contains(translated, instance.getAttributeSet());
}
public boolean contains(Location pt, Graphics g) {
InstanceTextField field = textField;
if (field != null && field.getBounds(g).contains(pt)) return true;
else return contains(pt);
}
//
// propagation methods
//
public List<EndData> getEnds() {
return endList;
}
public EndData getEnd(int index) {
return endArray[index];
}
public boolean endsAt(Location pt) {
EndData[] ends = endArray;
for (int i = 0; i < ends.length; i++) {
if (ends[i].getLocation().equals(pt)) return true;
}
return false;
}
public void propagate(CircuitState state) {
factory.propagate(state.getInstanceState(this));
}
//
// drawing methods
//
public void draw(ComponentDrawContext context) {
InstancePainter painter = context.getInstancePainter();
painter.setInstance(this);
factory.paintInstance(painter);
}
public void expose(ComponentDrawContext context) {
Bounds b = bounds;
context.getDestination().repaint(b.getX(), b.getY(), b.getWidth(), b.getHeight());
}
public String getToolTip(ComponentUserEvent e) {
int x = e.getX();
int y = e.getY();
int i = -1;
for (EndData end : endArray) {
i++;
if (end.getLocation().manhattanDistanceTo(x, y) < 10) {
Port p = portList.get(i);
return p.getToolTip();
}
}
StringGetter defaultTip = factory.getDefaultToolTip();
return defaultTip == null ? null : defaultTip.get();
}
//
// AttributeListener methods
//
public void attributeListChanged(AttributeEvent e) { }
public void attributeValueChanged(AttributeEvent e) {
Attribute<?> attr = e.getAttribute();
if (widthAttrs != null && widthAttrs.contains(attr)) computeEnds();
if (attrListenRequested) {
factory.instanceAttributeChanged(instance, e.getAttribute());
}
}
//
// methods for InstancePainter
//
void drawLabel(ComponentDrawContext context) {
InstanceTextField field = textField;
if (field != null) field.draw(this, context);
}
//
// methods for Instance
//
Instance getInstance() {
return instance;
}
List<Port> getPorts() {
return portList;
}
void setPorts(Port[] ports) {
Port[] portsCopy = ports.clone();
portList = new UnmodifiableList<Port>(portsCopy);
computeEnds();
}
void recomputeBounds() {
Location p = loc;
bounds = factory.getOffsetBounds(attrs).translate(p.getX(), p.getY());
}
void addAttributeListener(Instance instance) {
if (!attrListenRequested) {
attrListenRequested = true;
if (widthAttrs == null) getAttributeSet().addAttributeListener(this);
}
}
void setTextField(Attribute<String> labelAttr, Attribute<Font> fontAttr,
int x, int y, int halign, int valign) {
InstanceTextField field = textField;
if (field == null) {
field = new InstanceTextField(this);
field.update(labelAttr, fontAttr, x, y, halign, valign);
textField = field;
} else {
field.update(labelAttr, fontAttr, x, y, halign, valign);
}
}
}