diff --git a/src/main/java/de/neemann/digital/gui/Main.java b/src/main/java/de/neemann/digital/gui/Main.java index c8282804e..4983a9fdc 100644 --- a/src/main/java/de/neemann/digital/gui/Main.java +++ b/src/main/java/de/neemann/digital/gui/Main.java @@ -38,7 +38,13 @@ public class Main extends JFrame { parts.add(createSimpleMenu("OR", inputs -> Or.createFactory(1, inputs))); parts.add(createSimpleMenu("NAND", inputs -> NAnd.createFactory(1, inputs))); parts.add(createSimpleMenu("NOR", inputs -> NOr.createFactory(1, inputs))); - parts.add(new InsertAbstractAction("Not", Not.createFactory(1))); + parts.add(new InsertAction("Not", Not.createFactory(1))); + + JMenu edit = new JMenu("Edit"); + bar.add(edit); + edit.add(new JMenuItem(new ModeAction("Wire", CircuitComponent.Mode.wire))); + edit.add(new JMenuItem(new ModeAction("Parts", CircuitComponent.Mode.part))); + edit.add(new JMenuItem(new ModeAction("Move", CircuitComponent.Mode.move))); setJMenuBar(bar); } @@ -50,7 +56,7 @@ public class Main extends JFrame { private JMenu createSimpleMenu(String name, DescriptionFactory factory) { JMenu m = new JMenu(name); for (int i = 2; i < 16; i++) { - m.add(new JMenuItem(new InsertAbstractAction(Integer.toString(i), factory.create(i)))); + m.add(new JMenuItem(new InsertAction(Integer.toString(i), factory.create(i)))); } return m; } @@ -59,18 +65,33 @@ public class Main extends JFrame { PartDescription create(int inputs); } - private class InsertAbstractAction extends AbstractAction { + private class InsertAction extends AbstractAction { private final PartDescription partDescription; - public InsertAbstractAction(String name, PartDescription partDescription) { + public InsertAction(String name, PartDescription partDescription) { super(name); this.partDescription = partDescription; } @Override public void actionPerformed(ActionEvent e) { - cr.add(new VisualPart(partDescription).setPos(new Vector(10, 10))); + VisualPart visualPart = new VisualPart(partDescription).setPos(new Vector(10, 10)); + cr.add(visualPart); + circuitComponent.setPartToDrag(visualPart); } } + private class ModeAction extends AbstractAction { + private final CircuitComponent.Mode mode; + + public ModeAction(String name, CircuitComponent.Mode mode) { + super(name); + this.mode = mode; + } + + @Override + public void actionPerformed(ActionEvent e) { + circuitComponent.setMode(mode); + } + } } 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 77f7b3806..030e7d46e 100644 --- a/src/main/java/de/neemann/digital/gui/components/CircuitComponent.java +++ b/src/main/java/de/neemann/digital/gui/components/CircuitComponent.java @@ -1,30 +1,79 @@ package de.neemann.digital.gui.components; -import de.neemann.digital.gui.draw.graphics.GraphicSwing; -import de.neemann.digital.gui.draw.graphics.Vector; +import de.neemann.digital.gui.draw.graphics.*; +import de.neemann.digital.gui.draw.graphics.Polygon; import de.neemann.digital.gui.draw.parts.Circuit; +import de.neemann.digital.gui.draw.parts.Moveable; import de.neemann.digital.gui.draw.parts.VisualPart; +import de.neemann.digital.gui.draw.parts.Wire; import de.neemann.digital.gui.draw.shapes.GenericShape; import javax.swing.*; import java.awt.*; +import java.awt.event.ActionEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; +import java.util.ArrayList; /** * @author hneemann */ public class CircuitComponent extends JComponent { + private static final String delAction = "myDelAction"; private final Circuit circuit; - + ; + private Mouse listener; public CircuitComponent(Circuit circuit) { this.circuit = circuit; + setMode(Mode.part); - MyMouseMotionListener l = new MyMouseMotionListener(); - addMouseMotionListener(l); - addMouseListener(l); + KeyStroke delKey = KeyStroke.getKeyStroke("DELETE"); + getInputMap().put(delKey, delAction); + getActionMap().put(delAction, new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + if (listener instanceof MoveMouseListener) { + MoveMouseListener mml = (MoveMouseListener) listener; + if (mml.corner1 != null && mml.corner2 != null) { + circuit.delete(Vector.min(mml.corner1, mml.corner2), Vector.max(mml.corner1, mml.corner2)); + mml.reset(); + repaint(); + } + } + } + }); + + } + + public void setMode(Mode mode) { + if (listener != null) { + removeMouseListener(listener); + removeMouseMotionListener(listener); + } + switch (mode) { + case part: + listener = new PartMouseListener(); + setCursor(new Cursor(Cursor.HAND_CURSOR)); + break; + case wire: + listener = new WireMouseListener(); + setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR)); + break; + case move: + listener = new MoveMouseListener(); + setCursor(new Cursor(Cursor.MOVE_CURSOR)); + break; + } + addMouseMotionListener(listener); + addMouseListener(listener); + repaint(); + } + + public void setPartToDrag(VisualPart part) { + setMode(Mode.part); + ((PartMouseListener) listener).setPartToDrag(part); } @Override @@ -35,6 +84,8 @@ public class CircuitComponent extends JComponent { GraphicSwing gr = new GraphicSwing((Graphics2D) g); circuit.drawTo(gr); + + listener.drawTo(gr); } private Vector raster(Vector pos) { @@ -42,21 +93,84 @@ public class CircuitComponent extends JComponent { ((pos.y + GenericShape.SIZE2) / GenericShape.SIZE) * GenericShape.SIZE); } - private class MyMouseMotionListener implements MouseMotionListener, MouseListener { + public enum Mode {part, move, wire} + + private interface Mouse extends MouseMotionListener, MouseListener { + void drawTo(Graphic gr); + } + + private class WireMouseListener implements Mouse { + + private Wire wire; + + @Override + public void mouseClicked(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + if (wire != null) { + circuit.add(wire); + repaint(); + } + Vector startPos = raster(new Vector(e.getX(), e.getY())); + wire = new Wire(startPos, startPos); + repaint(); + } else { + wire = null; + repaint(); + } + } + + @Override + public void mousePressed(MouseEvent e) { + } + + @Override + public void mouseReleased(MouseEvent e) { + + } + + @Override + public void mouseEntered(MouseEvent e) { + + } + + @Override + public void mouseExited(MouseEvent e) { + + } + + @Override + public void mouseDragged(MouseEvent e) { + } + + @Override + public void mouseMoved(MouseEvent e) { + if (wire != null) { + wire.setP2(raster(new Vector(e.getX(), e.getY()))); + repaint(); + } + } + + @Override + public void drawTo(Graphic gr) { + if (wire != null) + wire.drawTo(gr); + } + } + + private class PartMouseListener implements Mouse { - private Vector lastPos; private VisualPart partToDrag; + private boolean autoPick = false; + private Vector delta; @Override public void mouseDragged(MouseEvent e) { Vector pos = new Vector(e.getX(), e.getY()); if (partToDrag != null) { - partToDrag.move(pos.sub(lastPos)); + partToDrag.setPos(raster(pos.add(delta))); repaint(); } - - lastPos = pos; } @Override @@ -70,28 +184,129 @@ public class CircuitComponent extends JComponent { @Override public void mousePressed(MouseEvent e) { - lastPos = new Vector(e.getX(), e.getY()); + Vector pos = new Vector(e.getX(), e.getY()); for (VisualPart vp : circuit.getParts()) - if (vp.matches(lastPos)) { + if (vp.matches(pos)) { partToDrag = vp; + delta = partToDrag.getPos().sub(pos); break; } } @Override public void mouseReleased(MouseEvent e) { - partToDrag.setPos(raster(partToDrag.getPos())); - repaint(); - partToDrag = null; + if (partToDrag != null) { + partToDrag.setPos(raster(partToDrag.getPos())); + repaint(); + partToDrag = null; + } } @Override public void mouseEntered(MouseEvent e) { + if (autoPick && partToDrag != null) { + partToDrag.setPos(raster(new Vector(e.getX(), e.getY()))); + autoPick = false; + repaint(); + } } @Override public void mouseExited(MouseEvent e) { } + + public void setPartToDrag(VisualPart partToDrag) { + this.partToDrag = partToDrag; + autoPick = true; + } + + @Override + public void drawTo(Graphic gr) { + } + } + + private class MoveMouseListener implements Mouse { + private Vector corner1; + private Vector corner2; + private ArrayList elementsToMove; + private Vector lastPos; + + @Override + public void mouseClicked(MouseEvent e) { + reset(); + repaint(); + } + + private void reset() { + corner1 = null; + corner2 = null; + elementsToMove = null; + } + + @Override + public void mousePressed(MouseEvent e) { + if (corner1 == null) { + corner1 = new Vector(e.getX(), e.getY()); + } else { + elementsToMove = circuit.getElementsMatching(Vector.min(corner1, corner2), Vector.max(corner1, corner2)); + lastPos = new Vector(e.getX(), e.getY()); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + + } + + @Override + public void mouseEntered(MouseEvent e) { + + } + + @Override + public void mouseExited(MouseEvent e) { + + } + + @Override + public void mouseDragged(MouseEvent e) { + if (elementsToMove != null) { + Vector pos = new Vector(e.getX(), e.getY()); + Vector delta = raster(pos.sub(lastPos)); + + if (delta.x != 0 || delta.y != 0) { + + for (Moveable m : elementsToMove) + m.move(delta); + + corner1.move(delta); + corner2.move(delta); + repaint(); + + lastPos = lastPos.add(delta); + } + } else { + corner2 = new Vector(e.getX(), e.getY()); + repaint(); + } + } + + @Override + public void mouseMoved(MouseEvent e) { + + } + + @Override + public void drawTo(Graphic gr) { + if (corner1 != null && corner2 != null) { + Polygon p = new Polygon(true) + .add(corner1) + .add(new Vector(corner1.x, corner2.y)) + .add(corner2) + .add(new Vector(corner2.x, corner1.y)); + gr.drawPolygon(p, Style.THIN); + } + } } } diff --git a/src/main/java/de/neemann/digital/gui/draw/graphics/Style.java b/src/main/java/de/neemann/digital/gui/draw/graphics/Style.java index 3a78ff145..1fd5eee11 100644 --- a/src/main/java/de/neemann/digital/gui/draw/graphics/Style.java +++ b/src/main/java/de/neemann/digital/gui/draw/graphics/Style.java @@ -7,8 +7,10 @@ import java.awt.*; */ public class Style { public static final Style NORMAL = new Style(2, false, Color.BLACK); - public static final Style WIRE = new Style(2, false, Color.BLUE); + public static final Style WIRE = new Style(2, true, Color.BLUE); public static final Style FILLED = new Style(2, true, Color.BLACK); + public static final Style THIN = new Style(1, false, Color.BLACK); + ; private final int thickness; private final boolean filled; diff --git a/src/main/java/de/neemann/digital/gui/draw/graphics/Vector.java b/src/main/java/de/neemann/digital/gui/draw/graphics/Vector.java index 4dd8a3024..73628ef59 100644 --- a/src/main/java/de/neemann/digital/gui/draw/graphics/Vector.java +++ b/src/main/java/de/neemann/digital/gui/draw/graphics/Vector.java @@ -1,12 +1,14 @@ package de.neemann.digital.gui.draw.graphics; +import de.neemann.digital.gui.draw.parts.Moveable; + /** * @author hneemann */ -public class Vector { +public class Vector implements Moveable { - public final int x; - public final int y; + public int x; + public int y; public Vector(int x, int y) { this.x = x; @@ -66,4 +68,14 @@ public class Vector { ", y=" + y + '}'; } + + @Override + public void move(Vector delta) { + x += delta.x; + y += delta.y; + } + + public boolean inside(Vector min, Vector max) { + return min.x <= x && x <= max.x && min.y <= y && y <= max.y; + } } diff --git a/src/main/java/de/neemann/digital/gui/draw/parts/Circuit.java b/src/main/java/de/neemann/digital/gui/draw/parts/Circuit.java index 5ae2dfb76..09e9c5039 100644 --- a/src/main/java/de/neemann/digital/gui/draw/parts/Circuit.java +++ b/src/main/java/de/neemann/digital/gui/draw/parts/Circuit.java @@ -1,9 +1,11 @@ package de.neemann.digital.gui.draw.parts; import de.neemann.digital.gui.draw.graphics.Graphic; +import de.neemann.digital.gui.draw.graphics.Vector; import de.neemann.digital.gui.draw.shapes.Drawable; import java.util.ArrayList; +import java.util.Iterator; /** * @author hneemann @@ -30,7 +32,44 @@ public class Circuit implements Drawable { visualParts.add(visualPart); } + public void add(Wire wire) { + wires.add(wire); + } + public ArrayList getParts() { return visualParts; } + + public ArrayList getElementsMatching(Vector min, Vector max) { + ArrayList m = new ArrayList<>(); + for (VisualPart vp : visualParts) + if (vp.matches(min, max)) + m.add(vp); + + for (Wire w : wires) { + if (w.p1.inside(min, max)) + m.add(w.p1); + if (w.p2.inside(min, max)) + m.add(w.p2); + } + + return m; + } + + public void delete(Vector min, Vector max) { + { + Iterator it = visualParts.iterator(); + while (it.hasNext()) + if (it.next().matches(min, max)) + it.remove(); + } + { + Iterator it = wires.iterator(); + while (it.hasNext()) { + Wire w = it.next(); + if (w.p1.inside(min, max) || w.p2.inside(min, max)) + it.remove(); + } + } + } } diff --git a/src/main/java/de/neemann/digital/gui/draw/parts/VisualPart.java b/src/main/java/de/neemann/digital/gui/draw/parts/VisualPart.java index fcae29f60..a640fdb0b 100644 --- a/src/main/java/de/neemann/digital/gui/draw/parts/VisualPart.java +++ b/src/main/java/de/neemann/digital/gui/draw/parts/VisualPart.java @@ -37,6 +37,15 @@ public class VisualPart implements Drawable, Moveable { (p.y <= m.getMax().y); } + public boolean matches(Vector min, Vector max) { + GraphicMinMax m = getMinMax(); + return (min.x <= m.getMin().x) && + (m.getMax().x <= max.x) && + (min.y <= m.getMin().y) && + (m.getMax().y <= max.y); + } + + public int getRotate() { return rotate; } diff --git a/src/main/java/de/neemann/digital/gui/draw/parts/Wire.java b/src/main/java/de/neemann/digital/gui/draw/parts/Wire.java index c99fc0326..280dc6227 100644 --- a/src/main/java/de/neemann/digital/gui/draw/parts/Wire.java +++ b/src/main/java/de/neemann/digital/gui/draw/parts/Wire.java @@ -10,8 +10,13 @@ import de.neemann.digital.gui.draw.shapes.Drawable; */ public class Wire implements Drawable, Moveable { - private Vector p1; - private Vector p2; + public Vector p1; + public Vector p2; + + public Wire(Vector p1, Vector p2) { + this.p1 = p1; + this.p2 = p2; + } @Override public void drawTo(Graphic graphic) { @@ -23,4 +28,8 @@ public class Wire implements Drawable, Moveable { p1 = p1.add(delta); p2 = p2.add(delta); } + + public void setP2(Vector p2) { + this.p2 = p2; + } } diff --git a/src/main/java/de/neemann/digital/gui/draw/shapes/GenericShape.java b/src/main/java/de/neemann/digital/gui/draw/shapes/GenericShape.java index 4881a594f..3ed9367c3 100644 --- a/src/main/java/de/neemann/digital/gui/draw/shapes/GenericShape.java +++ b/src/main/java/de/neemann/digital/gui/draw/shapes/GenericShape.java @@ -19,9 +19,12 @@ public class GenericShape implements Shape { private final String name; private final int inputs; private final int outputs; - private transient ArrayList pins; + private final int width; + private final boolean symetric; private boolean invert = false; + private transient ArrayList pins; + public GenericShape(String name, int inputs) { this(name, inputs, 1); } @@ -30,6 +33,8 @@ public class GenericShape implements Shape { this.name = name; this.inputs = inputs; this.outputs = outputs; + width = inputs == 1 && outputs == 1 ? 1 : 3; + symetric = outputs == 1; } public GenericShape invert(boolean invert) { @@ -43,15 +48,25 @@ public class GenericShape implements Shape { ObservableValue[] outputValues = partDescription.create().getOutputs(); String[] inputs = partDescription.getInputNames(); pins = new ArrayList<>(inputs.length + outputs); - for (int i = 0; i < inputs.length; i++) - pins.add(new Pin(new Vector(0, i * SIZE), inputs[i], Pin.Direction.input)); + + int offs = symetric ? inputs.length / 2 * SIZE : 0; + + for (int i = 0; i < inputs.length; i++) { + int correct = 0; + if (symetric && ((inputs.length & 1) == 0) && i >= inputs.length / 2) + correct = SIZE; + + pins.add(new Pin(new Vector(0, i * SIZE + correct), inputs[i], Pin.Direction.input)); + } + + if (invert) { for (int i = 0; i < outputs; i++) - pins.add(new Pin(new Vector(SIZE * 4, i * SIZE), outputValues[i].getName(), Pin.Direction.output)); + pins.add(new Pin(new Vector(SIZE * (width + 1), i * SIZE + offs), outputValues[i].getName(), Pin.Direction.output)); } else { for (int i = 0; i < outputs; i++) - pins.add(new Pin(new Vector(SIZE * 3, i * SIZE), outputValues[i].getName(), Pin.Direction.output)); + pins.add(new Pin(new Vector(SIZE * width, i * SIZE + offs), outputValues[i].getName(), Pin.Direction.output)); } } return pins; @@ -61,15 +76,20 @@ public class GenericShape implements Shape { public void drawTo(Graphic graphic) { int max = Math.max(inputs, outputs); int height = (max - 1) * SIZE + SIZE2; + + if (symetric && ((inputs & 1) == 0)) height += SIZE; + graphic.drawPolygon(new Polygon(true) .add(1, -SIZE2) - .add(SIZE * 3 - 1, -SIZE2) - .add(SIZE * 3 - 1, height) + .add(SIZE * width - 1, -SIZE2) + .add(SIZE * width - 1, height) .add(1, height), Style.NORMAL); if (invert) { + int offs = symetric ? inputs / 2 * SIZE : 0; for (int i = 0; i < outputs; i++) - graphic.drawCircle(new Vector(SIZE * 3, i * SIZE - SIZE2 + 1), new Vector(SIZE * 4 - 2, i * SIZE + SIZE2 - 1), Style.NORMAL); + graphic.drawCircle(new Vector(SIZE * width, i * SIZE - SIZE2 + 1 + offs), + new Vector(SIZE * (width + 1) - 2, i * SIZE + SIZE2 - 1 + offs), Style.NORMAL); }