/* 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.gui.main;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.cburch.logisim.circuit.Circuit;
import com.cburch.logisim.circuit.Wire;
import com.cburch.logisim.comp.Component;
import com.cburch.logisim.data.AbstractAttributeSet;
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.proj.Project;
import com.cburch.logisim.util.UnmodifiableList;
class SelectionAttributes extends AbstractAttributeSet {
private static final Attribute<?>[] EMPTY_ATTRIBUTES = new Attribute<?>[0];
private static final Object[] EMPTY_VALUES = new Object[0];
private class Listener implements Selection.Listener, AttributeListener {
public void selectionChanged(Selection.Event e) {
updateList(true);
}
public void attributeListChanged(AttributeEvent e) {
if (listening) {
updateList(false);
}
}
public void attributeValueChanged(AttributeEvent e) {
if (listening) {
updateList(false);
}
}
}
private Canvas canvas;
private Selection selection;
private Listener listener;
private boolean listening;
private Set<Component> selected;
private Attribute<?>[] attrs;
private boolean[] readOnly;
private Object[] values;
private List<Attribute<?>> attrsView;
public SelectionAttributes(Canvas canvas, Selection selection) {
this.canvas = canvas;
this.selection = selection;
this.listener = new Listener();
this.listening = true;
this.selected = Collections.emptySet();
this.attrs = EMPTY_ATTRIBUTES;
this.values = EMPTY_VALUES;
this.attrsView = Collections.emptyList();
selection.addListener(listener);
updateList(true);
setListening(true);
}
public Selection getSelection() {
return selection;
}
void setListening(boolean value) {
if (listening != value) {
listening = value;
if (value) {
updateList(false);
}
}
}
private void updateList(boolean ignoreIfSelectionSame) {
Selection sel = selection;
Set<Component> oldSel = selected;
Set<Component> newSel;
if (sel == null) newSel = Collections.emptySet();
else newSel = createSet(sel.getComponents());
if (haveSameElements(newSel, oldSel)) {
if (ignoreIfSelectionSame) return;
newSel = oldSel;
} else {
for (Component o : oldSel) {
if (!newSel.contains(o)) {
o.getAttributeSet().removeAttributeListener(listener);
}
}
for (Component o : newSel) {
if (!oldSel.contains(o)) {
o.getAttributeSet().addAttributeListener(listener);
}
}
}
LinkedHashMap<Attribute<Object>,Object> attrMap = computeAttributes(newSel);
boolean same = isSame(attrMap, this.attrs, this.values);
if (same) {
if (newSel != oldSel) this.selected = newSel;
} else {
Attribute<?>[] oldAttrs = this.attrs;
Object[] oldValues = this.values;
Attribute<?>[] newAttrs = new Attribute[attrMap.size()];
Object[] newValues = new Object[newAttrs.length];
boolean[] newReadOnly = new boolean[newAttrs.length];
int i = -1;
for (Map.Entry<Attribute<Object>,Object> entry : attrMap.entrySet()) {
i++;
newAttrs[i] = entry.getKey();
newValues[i] = entry.getValue();
newReadOnly[i] = computeReadOnly(newSel, newAttrs[i]);
}
if (newSel != oldSel) this.selected = newSel;
this.attrs = newAttrs;
this.attrsView = new UnmodifiableList<Attribute<?>>(newAttrs);
this.values = newValues;
this.readOnly = newReadOnly;
boolean listSame = oldAttrs != null && oldAttrs.length == newAttrs.length;
if (listSame) {
for (i = 0; i < oldAttrs.length; i++) {
if (!oldAttrs[i].equals(newAttrs[i])) {
listSame = false;
break;
}
}
}
if (listSame) {
for (i = 0; i < oldValues.length; i++) {
Object oldVal = oldValues[i];
Object newVal = newValues[i];
boolean sameVals = oldVal == null ? newVal == null
: oldVal.equals(newVal);
if (!sameVals) {
@SuppressWarnings("unchecked")
Attribute<Object> attr = (Attribute<Object>) oldAttrs[i];
fireAttributeValueChanged(attr, newVal);
}
}
} else {
fireAttributeListChanged();
}
}
}
private static Set<Component> createSet(Collection<Component> comps) {
boolean includeWires = true;
for (Component comp : comps) {
if (!(comp instanceof Wire)) { includeWires = false; break; }
}
if (includeWires) {
return new HashSet<Component>(comps);
} else {
HashSet<Component> ret = new HashSet<Component>();
for (Component comp : comps) {
if (!(comp instanceof Wire)) ret.add(comp);
}
return ret;
}
}
private static boolean haveSameElements(Collection<Component> a, Collection<Component> b) {
if (a == null) {
return b == null ? true : b.size() == 0;
} else if (b == null) {
return a.size() == 0;
} else if (a.size() != b.size()) {
return false;
} else {
for (Component item : a) {
if (!b.contains(item)) return false;
}
return true;
}
}
private static LinkedHashMap<Attribute<Object>,Object> computeAttributes(Collection<Component> newSel) {
LinkedHashMap<Attribute<Object>,Object> attrMap;
attrMap = new LinkedHashMap<Attribute<Object>,Object>();
Iterator<Component> sit = newSel.iterator();
if (sit.hasNext()) {
AttributeSet first = sit.next().getAttributeSet();
for (Attribute<?> attr : first.getAttributes()) {
@SuppressWarnings("unchecked")
Attribute<Object> attrObj = (Attribute<Object>) attr;
attrMap.put(attrObj, first.getValue(attr));
}
while (sit.hasNext()) {
AttributeSet next = sit.next().getAttributeSet();
Iterator<Attribute<Object>> ait = attrMap.keySet().iterator();
while (ait.hasNext()) {
Attribute<Object> attr = ait.next();
if (next.containsAttribute(attr)) {
Object v = attrMap.get(attr);
if (v != null && !v.equals(next.getValue(attr))) {
attrMap.put(attr, null);
}
} else {
ait.remove();
}
}
}
}
return attrMap;
}
private static boolean isSame(LinkedHashMap<Attribute<Object>,Object> attrMap,
Attribute<?>[] oldAttrs, Object[] oldValues) {
if (oldAttrs.length != attrMap.size()) {
return false;
} else {
int j = -1;
for (Map.Entry<Attribute<Object>,Object> entry : attrMap.entrySet()) {
j++;
Attribute<Object> a = entry.getKey();
if (!oldAttrs[j].equals(a) || j >= oldValues.length) return false;
Object ov = oldValues[j];
Object nv = entry.getValue();
if (ov == null ? nv != null : !ov.equals(nv)) return false;
}
return true;
}
}
private static boolean computeReadOnly(Collection<Component> sel, Attribute<?> attr) {
for (Component comp : sel) {
AttributeSet attrs = comp.getAttributeSet();
if (attrs.isReadOnly(attr)) return true;
}
return false;
}
@Override
protected void copyInto(AbstractAttributeSet dest) {
throw new UnsupportedOperationException("SelectionAttributes.copyInto");
}
@Override
public List<Attribute<?>> getAttributes() {
Circuit circ = canvas.getCircuit();
if (selected.isEmpty() && circ != null) {
return circ.getStaticAttributes().getAttributes();
} else {
return attrsView;
}
}
@Override
public boolean isReadOnly(Attribute<?> attr) {
Project proj = canvas.getProject();
Circuit circ = canvas.getCircuit();
if (!proj.getLogisimFile().contains(circ)) {
return true;
} else if (selected.isEmpty() && circ != null) {
return circ.getStaticAttributes().isReadOnly(attr);
} else {
int i = findIndex(attr);
boolean[] ro = readOnly;
return i >= 0 && i < ro.length ? ro[i] : true;
}
}
@Override
public boolean isToSave(Attribute<?> attr) {
return false;
}
@Override
public <V> V getValue(Attribute<V> attr) {
Circuit circ = canvas.getCircuit();
if (selected.isEmpty() && circ != null) {
return circ.getStaticAttributes().getValue(attr);
} else {
int i = findIndex(attr);
Object[] vs = values;
@SuppressWarnings("unchecked")
V ret = (V) (i >= 0 && i < vs.length ? vs[i] : null);
return ret;
}
}
@Override
public <V> void setValue(Attribute<V> attr, V value) {
Circuit circ = canvas.getCircuit();
if (selected.isEmpty() && circ != null) {
circ.getStaticAttributes().setValue(attr, value);
} else {
int i = findIndex(attr);
Object[] vs = values;
if (i >= 0 && i < vs.length) {
vs[i] = value;
for (Component comp : selected) {
comp.getAttributeSet().setValue(attr, value);
}
}
}
}
private int findIndex(Attribute<?> attr) {
if (attr == null) return -1;
Attribute<?>[] as = attrs;
for (int i = 0; i < as.length; i++) {
if (attr.equals(as[i])) return i;
}
return -1;
}
}