diff --git a/src/main/java/de/neemann/digital/core/element/AttributeListener.java b/src/main/java/de/neemann/digital/core/element/AttributeListener.java index 488edca03..8cc824838 100644 --- a/src/main/java/de/neemann/digital/core/element/AttributeListener.java +++ b/src/main/java/de/neemann/digital/core/element/AttributeListener.java @@ -8,8 +8,6 @@ package de.neemann.digital.core.element; public interface AttributeListener { /** * Is called if an attribute changes - * - * @param key the key which value has changed */ - void attributeChanged(Key key); + void attributeChanged(); } diff --git a/src/main/java/de/neemann/digital/core/element/ElementAttributes.java b/src/main/java/de/neemann/digital/core/element/ElementAttributes.java index d88541d99..20c4596eb 100644 --- a/src/main/java/de/neemann/digital/core/element/ElementAttributes.java +++ b/src/main/java/de/neemann/digital/core/element/ElementAttributes.java @@ -90,15 +90,15 @@ public class ElementAttributes { attributes = new HashMap<>(); attributes.put(key.getKey(), value); } - fireValueChanged(key); + fireValueChanged(); } return this; } - private void fireValueChanged(Key key) { + private void fireValueChanged() { if (listeners != null) for (AttributeListener l : listeners) - l.attributeChanged(key); + l.attributeChanged(); } /** @@ -221,4 +221,22 @@ public class ElementAttributes { attributes.put(fileKey, file.getPath()); } + /** + * Apply the given attributes to this set + * + * @param elementAttributes the attributes to use + */ + public void getValuesFrom(ElementAttributes elementAttributes) { + if (attributes != null) + attributes.clear(); + else + attributes = new HashMap<>(); + + if (elementAttributes.attributes != null) + attributes.putAll(elementAttributes.attributes); + + if (attributes.isEmpty()) + attributes = null; + fireValueChanged(); + } } diff --git a/src/main/java/de/neemann/digital/draw/elements/Circuit.java b/src/main/java/de/neemann/digital/draw/elements/Circuit.java index 3f14f1c77..bcbe05ddd 100644 --- a/src/main/java/de/neemann/digital/draw/elements/Circuit.java +++ b/src/main/java/de/neemann/digital/draw/elements/Circuit.java @@ -146,6 +146,25 @@ public class Circuit { wires = new ArrayList<>(); } + /** + * Creates a copy of the given circuit + * + * @param original the original + */ + public Circuit(Circuit original) { + this(); + for (VisualElement ve : original.visualElements) + visualElements.add(new VisualElement(ve)); + for (Wire w : original.wires) + wires.add(new Wire(w)); + if (original.attributes != null) + attributes = new ElementAttributes(original.attributes); + + measurementOrdering = new ArrayList<>(); + if (original.measurementOrdering != null) + measurementOrdering.addAll(original.measurementOrdering); + } + /** * returns the elements attributes * diff --git a/src/main/java/de/neemann/digital/draw/elements/ElementOrder.java b/src/main/java/de/neemann/digital/draw/elements/ElementOrder.java index 0967b9ae9..47e34526a 100644 --- a/src/main/java/de/neemann/digital/draw/elements/ElementOrder.java +++ b/src/main/java/de/neemann/digital/draw/elements/ElementOrder.java @@ -1,7 +1,9 @@ package de.neemann.digital.draw.elements; import de.neemann.digital.core.element.ElementTypeDescription; +import de.neemann.digital.gui.components.CircuitComponent; import de.neemann.digital.gui.components.ElementOrderer; +import de.neemann.digital.gui.components.modification.Modification; import java.util.ArrayList; @@ -15,16 +17,16 @@ public class ElementOrder implements ElementOrderer.OrderInterface { private final ArrayList entries; private final ArrayList elements; - private final Circuit circuit; + private final CircuitComponent circuitComponent; /** * Creates a new instance * - * @param circuit the circuit witch components are to order - * @param description the description of the elements to order + * @param circuitComponent the circuit witch components are to order + * @param description the description of the elements to order */ - public ElementOrder(Circuit circuit, ElementTypeDescription description) { - this(circuit, element -> { + public ElementOrder(CircuitComponent circuitComponent, ElementTypeDescription description) { + this(circuitComponent, element -> { return element.equalsDescription(description); }); } @@ -32,12 +34,12 @@ public class ElementOrder implements ElementOrderer.OrderInterface { /** * Creates a new instance * - * @param circuit the circuit witch components are to order - * @param filter the filter to select the entries to order + * @param circuitComponent the circuitComponent witch components are to order + * @param filter the filter to select the entries to order */ - public ElementOrder(Circuit circuit, ElementFilter filter) { - this.circuit = circuit; - this.elements = circuit.getElements(); + public ElementOrder(CircuitComponent circuitComponent, ElementFilter filter) { + this.circuitComponent = circuitComponent; + this.elements = circuitComponent.getCircuit().getElements(); entries = new ArrayList<>(); for (int i = 0; i < elements.size(); i++) if (filter.accept(elements.get(i))) { @@ -59,9 +61,8 @@ public class ElementOrder implements ElementOrderer.OrderInterface { @Override public void swap(int i, int j) { - VisualElement y = elements.get(entries.get(i).i); - elements.set(entries.get(i).i, elements.get(entries.get(j).i)); - elements.set(entries.get(j).i, y); + int index1 = entries.get(i).i; + int index2 = entries.get(j).i; int z = entries.get(i).i; entries.get(i).i = entries.get(j).i; @@ -71,7 +72,11 @@ public class ElementOrder implements ElementOrderer.OrderInterface { entries.set(i, entries.get(j)); entries.set(j, x); - circuit.modified(); + circuitComponent.modify(circuit -> { + VisualElement y = elements.get(index1); + elements.set(index1, elements.get(index2)); + elements.set(index2, y); + }); } private final static class Entry { diff --git a/src/main/java/de/neemann/digital/draw/elements/VisualElement.java b/src/main/java/de/neemann/digital/draw/elements/VisualElement.java index ec54d2634..b7bfbd69f 100644 --- a/src/main/java/de/neemann/digital/draw/elements/VisualElement.java +++ b/src/main/java/de/neemann/digital/draw/elements/VisualElement.java @@ -61,6 +61,7 @@ public class VisualElement implements Drawable, Movable, AttributeListener { this.elementName = proto.elementName; this.elementAttributes = new ElementAttributes(proto.elementAttributes); setPos(new Vector(proto.pos)); + this.shapeFactory=proto.shapeFactory; } /** @@ -82,7 +83,7 @@ public class VisualElement implements Drawable, Movable, AttributeListener { } @Override - public void attributeChanged(Key key) { + public void attributeChanged() { shape = null; resetGeometry(); } diff --git a/src/main/java/de/neemann/digital/gui/Main.java b/src/main/java/de/neemann/digital/gui/Main.java index 7fa73703b..50fbf1c17 100644 --- a/src/main/java/de/neemann/digital/gui/Main.java +++ b/src/main/java/de/neemann/digital/gui/Main.java @@ -104,6 +104,7 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS private static final Icon ICON_ZOOM_IN = IconCreator.create("View-zoom-in.png"); private static final Icon ICON_ZOOM_OUT = IconCreator.create("View-zoom-out.png"); private static final Icon ICON_HELP = IconCreator.create("help.png"); + private final CircuitComponent circuitComponent; private final ToolTipAction save; private final ElementLibrary library; @@ -193,6 +194,8 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS createEditMenu(menuBar); + toolBar.add(circuitComponent.getUndoAction().createJButtonNoText()); + toolBar.add(circuitComponent.getRedoAction().createJButtonNoText()); toolBar.add(circuitComponent.getDeleteAction().createJButtonNoText()); toolBar.addSeparator(); @@ -473,7 +476,7 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS ToolTipAction orderInputs = new ToolTipAction(Lang.get("menu_orderInputs")) { @Override public void actionPerformed(ActionEvent e) { - ElementOrder o = new ElementOrder(circuitComponent.getCircuit(), + ElementOrder o = new ElementOrder(circuitComponent, element -> element.equalsDescription(In.DESCRIPTION) || element.equalsDescription(Clock.DESCRIPTION)); new ElementOrderer<>(Main.this, Lang.get("menu_orderInputs"), o).showDialog(); @@ -483,7 +486,7 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS ToolTipAction orderOutputs = new ToolTipAction(Lang.get("menu_orderOutputs")) { @Override public void actionPerformed(ActionEvent e) { - ElementOrder o = new ElementOrder(circuitComponent.getCircuit(), + ElementOrder o = new ElementOrder(circuitComponent, element -> element.equalsDescription(Out.DESCRIPTION) || element.equalsDescription(Out.LEDDESCRIPTION)); new ElementOrderer<>(Main.this, Lang.get("menu_orderOutputs"), o).showDialog(); diff --git a/src/main/java/de/neemann/digital/gui/NumberingWizard.java b/src/main/java/de/neemann/digital/gui/NumberingWizard.java index 19275af5f..af677956f 100644 --- a/src/main/java/de/neemann/digital/gui/NumberingWizard.java +++ b/src/main/java/de/neemann/digital/gui/NumberingWizard.java @@ -6,6 +6,7 @@ import de.neemann.digital.core.io.Out; import de.neemann.digital.core.wiring.Clock; import de.neemann.digital.draw.elements.VisualElement; import de.neemann.digital.gui.components.CircuitComponent; +import de.neemann.digital.gui.components.modification.ModifyAttribute; import de.neemann.digital.lang.Lang; import de.neemann.gui.Screen; @@ -71,11 +72,9 @@ public class NumberingWizard extends JDialog implements CircuitComponent.WizardN if (clicked.equalsDescription(In.DESCRIPTION) || clicked.equalsDescription(Clock.DESCRIPTION) || clicked.equalsDescription(Out.DESCRIPTION)) { - clicked.getElementAttributes().set(Keys.PINNUMBER, pinNumber); + circuitComponent.modify(new ModifyAttribute<>(clicked, Keys.PINNUMBER, pinNumber)); pinNumber++; setPinNumber(pinNumber); - circuitComponent.hasChanged(); - circuitComponent.getCircuit().modified(); } } diff --git a/src/main/java/de/neemann/digital/gui/Settings.java b/src/main/java/de/neemann/digital/gui/Settings.java index 8571b9a20..bc352dd52 100644 --- a/src/main/java/de/neemann/digital/gui/Settings.java +++ b/src/main/java/de/neemann/digital/gui/Settings.java @@ -89,7 +89,7 @@ public final class Settings implements AttributeListener { } @Override - public void attributeChanged(Key key) { + public void attributeChanged() { XStream xStream = Circuit.getxStream(); try (Writer out = new OutputStreamWriter(new FileOutputStream(filename), "utf-8")) { out.write("\n"); diff --git a/src/main/java/de/neemann/digital/gui/components/CircuitComponent.java b/src/main/java/de/neemann/digital/gui/components/CircuitComponent.java index 33c356793..3cdafe6e5 100644 --- a/src/main/java/de/neemann/digital/gui/components/CircuitComponent.java +++ b/src/main/java/de/neemann/digital/gui/components/CircuitComponent.java @@ -8,6 +8,8 @@ import de.neemann.digital.core.element.ImmutableList; import de.neemann.digital.core.element.Key; import de.neemann.digital.core.element.Keys; import de.neemann.digital.draw.elements.*; +import de.neemann.digital.gui.components.modification.Modification; +import de.neemann.digital.gui.components.modification.ModifyAttribute; import de.neemann.digital.draw.graphics.*; import de.neemann.digital.draw.library.ElementLibrary; import de.neemann.digital.draw.library.ElementNotFoundException; @@ -16,6 +18,7 @@ import de.neemann.digital.draw.library.LibraryNode; import de.neemann.digital.draw.shapes.Drawable; import de.neemann.digital.draw.shapes.ShapeFactory; import de.neemann.digital.gui.Main; +import de.neemann.digital.gui.components.modification.ModifyAttributes; import de.neemann.digital.gui.sync.NoSync; import de.neemann.digital.gui.sync.Sync; import de.neemann.digital.lang.Lang; @@ -53,6 +56,9 @@ public class CircuitComponent extends JComponent implements Circuit.ChangedListe * The delete icon, also used from {@link de.neemann.digital.gui.components.terminal.TerminalDialog} */ public static final Icon ICON_DELETE = IconCreator.create("delete.png"); + private static final Icon ICON_UNDO = IconCreator.create("edit-undo.png"); + private static final Icon ICON_REDO = IconCreator.create("edit-redo.png"); + private static final String DEL_ACTION = "myDelAction"; private static final String ESC_ACTION = "myEscAction"; private static final int MOUSE_BORDER_SMALL = 10; @@ -76,6 +82,8 @@ public class CircuitComponent extends JComponent implements Circuit.ChangedListe private final AbstractAction copyAction; private final AbstractAction pasteAction; private final AbstractAction rotateAction; + private final ToolTipAction undoAction; + private final ToolTipAction redoAction; private Circuit circuit; private MouseController activeMouseController; @@ -89,6 +97,10 @@ public class CircuitComponent extends JComponent implements Circuit.ChangedListe private boolean lockMessageShown = false; private boolean antiAlias = true; + private ArrayList modifications; + private Circuit initialCircuit; + private int undoPosition; + /** * Creates a new instance @@ -162,6 +174,20 @@ public class CircuitComponent extends JComponent implements Circuit.ChangedListe } }.setToolTip(Lang.get("menu_delete_tt")); + undoAction = new ToolTipAction(Lang.get("menu_undo"), ICON_UNDO) { + @Override + public void actionPerformed(ActionEvent actionEvent) { + undo(); + } + }.setToolTip(Lang.get("menu_undo_tt")); + + redoAction = new ToolTipAction(Lang.get("menu_redo"), ICON_REDO) { + @Override + public void actionPerformed(ActionEvent actionEvent) { + redo(); + } + }.setToolTip(Lang.get("menu_redo_tt")); + Action escapeAction = new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { @@ -244,12 +270,36 @@ public class CircuitComponent extends JComponent implements Circuit.ChangedListe VisualElement ve = circuit.getElementAt(pos); if (ve != null && library.isProgrammable(ve.getElementName())) { boolean blown = ve.getElementAttributes().get(Keys.BLOWN); - ve.getElementAttributes().set(Keys.BLOWN, !blown); - circuit.modified(); - hasChanged(); + modify(new ModifyAttribute<>(ve, Keys.BLOWN, !blown)); } } + /** + * Apply a modification + * + * @param modification the modification + */ + public void modify(Modification modification) { + modification.modify(circuit); + addModificationAlreadyMade(modification); + } + + /** + * Add a modification already made + * + * @param modification the modification + */ + public void addModificationAlreadyMade(Modification modification) { + while (modifications.size() > undoPosition) + modifications.remove(modifications.size() - 1); + redoAction.setEnabled(false); + modifications.add(modification); + undoPosition = modifications.size(); + undoAction.setEnabled(true); + circuit.modified(); + hasChanged(); + } + /** * invalidates the image buffer and calls repaint(); */ @@ -258,6 +308,38 @@ public class CircuitComponent extends JComponent implements Circuit.ChangedListe repaint(); } + /** + * undo last action + */ + public void undo() { + if (undoPosition > 0) { + circuit = new Circuit(initialCircuit); + undoPosition--; + for (int i = 0; i < undoPosition; i++) + modifications.get(i).modify(circuit); + redoAction.setEnabled(true); + if (undoPosition == 0) + undoAction.setEnabled(false); + circuit.modified(); + hasChanged(); + } + } + + /** + * redo last undo + */ + public void redo() { + if (undoPosition < modifications.size()) { + modifications.get(undoPosition).modify(circuit); + undoPosition++; + if (undoPosition == modifications.size()) + redoAction.setEnabled(false); + undoAction.setEnabled(true); + hasChanged(); + } + } + + /** * @return the main frame */ @@ -559,6 +641,11 @@ public class CircuitComponent extends JComponent implements Circuit.ChangedListe } this.circuit = circuit; + modifications = new ArrayList<>(); + initialCircuit = new Circuit(circuit); + undoPosition = 0; + undoAction.setEnabled(false); + redoAction.setEnabled(false); circuit.addListener(this); @@ -646,10 +733,8 @@ public class CircuitComponent extends JComponent implements Circuit.ChangedListe } } }.setToolTip(Lang.get("attr_help_tt"))); - if (attributeDialog.showDialog()) { - circuit.modified(); - hasChanged(); - } + if (attributeDialog.showDialog()) + addModificationAlreadyMade(new ModifyAttributes(vp)); } } catch (ElementNotFoundException ex) { // do nothing if element not found! @@ -679,6 +764,20 @@ public class CircuitComponent extends JComponent implements Circuit.ChangedListe return locked; } + /** + * @return undo action + */ + public ToolTipAction getUndoAction() { + return undoAction; + } + + /** + * @return redo action + */ + public ToolTipAction getRedoAction() { + return redoAction; + } + private class MouseDispatcher extends MouseAdapter implements MouseMotionListener { private Vector pos; private boolean isMoved; diff --git a/src/main/java/de/neemann/digital/gui/components/modification/Modification.java b/src/main/java/de/neemann/digital/gui/components/modification/Modification.java new file mode 100644 index 000000000..5e3c14c32 --- /dev/null +++ b/src/main/java/de/neemann/digital/gui/components/modification/Modification.java @@ -0,0 +1,12 @@ +package de.neemann.digital.gui.components.modification; + +import de.neemann.digital.draw.elements.Circuit; + +/** + * Created by hneemann on 25.05.17. + */ +public interface Modification { + + void modify(Circuit circuit); + +} diff --git a/src/main/java/de/neemann/digital/gui/components/modification/ModificationOfVisualElement.java b/src/main/java/de/neemann/digital/gui/components/modification/ModificationOfVisualElement.java new file mode 100644 index 000000000..d3a890aad --- /dev/null +++ b/src/main/java/de/neemann/digital/gui/components/modification/ModificationOfVisualElement.java @@ -0,0 +1,27 @@ +package de.neemann.digital.gui.components.modification; + +import de.neemann.digital.draw.elements.Circuit; +import de.neemann.digital.draw.elements.VisualElement; +import de.neemann.digital.draw.graphics.Vector; + +/** + * Created by hneemann on 25.05.17. + */ +public abstract class ModificationOfVisualElement implements Modification { + + private final Vector pos; + private final String name; + + public ModificationOfVisualElement(VisualElement ve) { + pos = ve.getPos(); + name = ve.getElementName(); + } + + public VisualElement getVisualElement(Circuit circuit) { + for (VisualElement ve : circuit.getElements()) { + if (ve.getPos().equals(pos) && ve.getElementName().equals(name)) + return ve; + } + throw new RuntimeException("internal error: Element not found!"); + } +} diff --git a/src/main/java/de/neemann/digital/gui/components/modification/Modifications.java b/src/main/java/de/neemann/digital/gui/components/modification/Modifications.java new file mode 100644 index 000000000..7b8496c84 --- /dev/null +++ b/src/main/java/de/neemann/digital/gui/components/modification/Modifications.java @@ -0,0 +1,39 @@ +package de.neemann.digital.gui.components.modification; + +import de.neemann.digital.draw.elements.Circuit; + +import java.util.ArrayList; + +/** + * Created by hneemann on 25.05.17. + */ +public final class Modifications implements Modification { + private final ArrayList modifications; + + private Modifications(ArrayList modifications) { + this.modifications = modifications; + } + + @Override + public void modify(Circuit circuit) { + for (Modification m : modifications) + m.modify(circuit); + } + + public static final class Builder { + private final ArrayList list; + + public Builder() { + list = new ArrayList<>(); + } + + public Builder add(Modification m) { + list.add(m); + return this; + } + + public Modification build() { + return new Modifications(list); + } + } +} diff --git a/src/main/java/de/neemann/digital/gui/components/modification/ModifyAttribute.java b/src/main/java/de/neemann/digital/gui/components/modification/ModifyAttribute.java new file mode 100644 index 000000000..830c9a6a3 --- /dev/null +++ b/src/main/java/de/neemann/digital/gui/components/modification/ModifyAttribute.java @@ -0,0 +1,26 @@ +package de.neemann.digital.gui.components.modification; + +import de.neemann.digital.core.element.Key; +import de.neemann.digital.draw.elements.Circuit; +import de.neemann.digital.draw.elements.VisualElement; + +/** + * Created by hneemann on 25.05.17. + */ +public class ModifyAttribute extends ModificationOfVisualElement { + + private final Key key; + private final VALUE value; + + public ModifyAttribute(VisualElement ve, Key key, VALUE value) { + super(ve); + this.key = key; + this.value = value; + } + + @Override + public void modify(Circuit circuit) { + VisualElement ve = getVisualElement(circuit); + ve.getElementAttributes().set(key, value); + } +} diff --git a/src/main/java/de/neemann/digital/gui/components/modification/ModifyAttributes.java b/src/main/java/de/neemann/digital/gui/components/modification/ModifyAttributes.java new file mode 100644 index 000000000..f5143fbca --- /dev/null +++ b/src/main/java/de/neemann/digital/gui/components/modification/ModifyAttributes.java @@ -0,0 +1,24 @@ +package de.neemann.digital.gui.components.modification; + +import de.neemann.digital.core.element.ElementAttributes; +import de.neemann.digital.draw.elements.Circuit; +import de.neemann.digital.draw.elements.VisualElement; + +/** + * Created by hneemann on 25.05.17. + */ +public class ModifyAttributes extends ModificationOfVisualElement { + + private final ElementAttributes attributes; + + public ModifyAttributes(VisualElement ve) { + super(ve); + attributes = new ElementAttributes(ve.getElementAttributes()); + } + + @Override + public void modify(Circuit circuit) { + VisualElement ve = getVisualElement(circuit); + ve.getElementAttributes().getValuesFrom(attributes); + } +} diff --git a/src/main/resources/edit-redo.png b/src/main/resources/edit-redo.png new file mode 100644 index 000000000..a5adb95df Binary files /dev/null and b/src/main/resources/edit-redo.png differ diff --git a/src/main/resources/edit-redo_hi.png b/src/main/resources/edit-redo_hi.png new file mode 100644 index 000000000..a44afb037 Binary files /dev/null and b/src/main/resources/edit-redo_hi.png differ diff --git a/src/main/resources/edit-undo.png b/src/main/resources/edit-undo.png new file mode 100644 index 000000000..684b517a5 Binary files /dev/null and b/src/main/resources/edit-undo.png differ diff --git a/src/main/resources/edit-undo_hi.png b/src/main/resources/edit-undo_hi.png new file mode 100644 index 000000000..a9bf51dff Binary files /dev/null and b/src/main/resources/edit-undo_hi.png differ diff --git a/src/main/svg/edit-redo.svg b/src/main/svg/edit-redo.svg new file mode 100644 index 000000000..bc4d52af7 --- /dev/null +++ b/src/main/svg/edit-redo.svg @@ -0,0 +1,231 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Jakub Steiner + + + http://jimmac.musichall.cz + + Edit Redo + + + edit + redo + again + reapply + + + + + + + + + + + + + + + + + diff --git a/src/main/svg/edit-undo.svg b/src/main/svg/edit-undo.svg new file mode 100644 index 000000000..f482565f0 --- /dev/null +++ b/src/main/svg/edit-undo.svg @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Jakub Steiner + + + http://jimmac.musichall.cz + + Edit Undo + + + edit + undo + revert + + + + + + + + + + + + + + + + + diff --git a/src/main/svg/exp.sh b/src/main/svg/exp.sh index ec6f0d6ff..adae63d02 100755 --- a/src/main/svg/exp.sh +++ b/src/main/svg/exp.sh @@ -16,6 +16,8 @@ ./expicon.sh View-zoom-fit ./expicon.sh View-zoom-in ./expicon.sh View-zoom-out +./expicon.sh edit-redo +./expicon.sh edit-undo ./exptest.sh testFailed ./exptest.sh testPassed