/******************************************************************************* * 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.instance; import java.awt.Color; 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 javax.swing.JOptionPane; import com.bfh.logisim.designrulecheck.CorrectLabel; import com.bfh.logisim.designrulecheck.Netlist; 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.GraphicsUtil; import com.cburch.logisim.util.StringGetter; import com.cburch.logisim.util.SyntaxChecker; import com.cburch.logisim.util.UnmodifiableList; public 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; private InstanceStateImpl instanceState; private boolean MarkInstance; private boolean MarkLabel; public 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; MarkInstance = false; MarkLabel = false; computeEnds(); } void addAttributeListener(Instance instance) { if (!attrListenRequested) { attrListenRequested = true; if (widthAttrs == null) getAttributeSet().addAttributeListener(this); } } // // DRC mark functions // public boolean isMarked() { return MarkInstance || MarkLabel; } public void clearMarks() { MarkInstance = false; MarkLabel = false; } public void MarkInstance() { MarkInstance = true; } public void MarkLabel() { MarkLabel = true; } // // 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); } } // // AttributeListener methods // public void attributeListChanged(AttributeEvent e) { } public void attributeValueChanged(AttributeEvent e) { Attribute<?> attr = e.getAttribute(); if (e.getAttribute().equals(StdAttr.LABEL)) { @SuppressWarnings("unchecked") Attribute<String> lattr = (Attribute<String>) e.getAttribute(); String value = (String) e.getSource().getValue(e.getAttribute()); String Oldvalue = e.getOldValue() != null ? (String) e.getOldValue() : ""; if (!Oldvalue.equals(value)) { if (!SyntaxChecker.isVariableNameAcceptable(value,true)) { e.getSource().setValue(lattr, Oldvalue); } else if (getFactory().getName().toUpperCase().equals(value.toUpperCase())) { JOptionPane.showMessageDialog(null, Strings.get("MatchedLabelNameError")); e.getSource().setValue(lattr, Oldvalue); } else if (CorrectLabel.IsKeyword(value,false)) { JOptionPane.showMessageDialog(null, "\""+value+"\": "+Strings.get("KeywordNameError")); e.getSource().setValue(lattr, Oldvalue); } else { fireLabelChanged(e); } } } if (widthAttrs != null && widthAttrs.contains(attr)) computeEnds(); if (attrListenRequested) { factory.instanceAttributeChanged(instance, e.getAttribute()); } } 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); } } 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); } // // drawing methods // public void draw(ComponentDrawContext context) { InstancePainter painter = context.getInstancePainter(); painter.setInstance(this); factory.paintInstance(painter); if (MarkInstance) { Graphics g = painter.getGraphics(); Bounds bds = painter.getBounds(); Color current = g.getColor(); g.setColor(Netlist.DRC_INSTANCE_MARK_COLOR); GraphicsUtil.switchToWidth(g, 2); g.drawRoundRect(bds.getX()-10, bds.getY()-10, bds.getWidth()+20, bds.getHeight()+20, 40, 40); GraphicsUtil.switchToWidth(g, 1); g.setColor(current); } } // // methods for InstancePainter // void drawLabel(ComponentDrawContext context) { InstanceTextField field = textField; if (field != null) { field.draw(this, context); if (MarkLabel) { Graphics g = context.getGraphics(); Bounds bds = field.getBounds(g); Color current = g.getColor(); g.setColor(Netlist.DRC_LABEL_MARK_COLOR); GraphicsUtil.switchToWidth(g, 2); g.drawRoundRect(bds.getX()-10, bds.getY()-10, bds.getWidth()+20, bds.getHeight()+20, 40, 40); GraphicsUtil.switchToWidth(g, 1); g.setColor(current); } } } 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 expose(ComponentDrawContext context) { Bounds b = bounds; context.getDestination().repaint(b.getX(), b.getY(), b.getWidth(), b.getHeight()); } private void fireLabelChanged(AttributeEvent attre) { EventSourceWeakSupport<ComponentListener> ls = listeners; if (ls != null) { ComponentEvent e = null; for (ComponentListener l : ls) { if (e == null) e = new ComponentEvent(this, null, attre); l.LabelChanged(e); } } } 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); } } } public AttributeSet getAttributeSet() { return attrs; } 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 EndData getEnd(int index) { return endArray[index]; } // // propagation methods // public List<EndData> getEnds() { return endList; } // // basic information methods // public ComponentFactory getFactory() { return factory; } 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; } // // methods for Instance // public Instance getInstance() { return instance; } public InstanceStateImpl getInstanceStateImpl() { return instanceState; } // // location/extent methods // public Location getLocation() { return loc; } List<Port> getPorts() { return portList; } 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.toString(); } public void propagate(CircuitState state) { factory.propagate(state.getInstanceState(this)); } void recomputeBounds() { Location p = loc; bounds = factory.getOffsetBounds(attrs).translate(p.getX(), p.getY()); } public void removeComponentListener(ComponentListener l) { if (listeners != null) { listeners.remove(l); if (listeners.isEmpty()) listeners = null; } } public void setInstanceStateImpl(InstanceStateImpl instanceState) { this.instanceState = instanceState; } void setPorts(Port[] ports) { Port[] portsCopy = ports.clone(); portList = new UnmodifiableList<Port>(portsCopy); computeEnds(); } 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); } } }