mirror of
https://github.com/hneemann/Digital.git
synced 2025-10-06 03:12:27 -04:00
1358 lines
43 KiB
Java
1358 lines
43 KiB
Java
package de.neemann.digital.gui.components;
|
|
|
|
import de.neemann.digital.core.NodeException;
|
|
import de.neemann.digital.core.ObservableValue;
|
|
import de.neemann.digital.core.Observer;
|
|
import de.neemann.digital.core.element.ElementTypeDescription;
|
|
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.draw.graphics.*;
|
|
import de.neemann.digital.draw.library.ElementLibrary;
|
|
import de.neemann.digital.draw.library.ElementNotFoundException;
|
|
import de.neemann.digital.draw.library.LibraryListener;
|
|
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.sync.NoSync;
|
|
import de.neemann.digital.gui.sync.Sync;
|
|
import de.neemann.digital.lang.Lang;
|
|
import de.neemann.gui.ErrorMessage;
|
|
import de.neemann.gui.IconCreator;
|
|
import de.neemann.gui.StringUtils;
|
|
import de.neemann.gui.ToolTipAction;
|
|
|
|
import javax.swing.*;
|
|
import java.awt.*;
|
|
import java.awt.datatransfer.Clipboard;
|
|
import java.awt.datatransfer.DataFlavor;
|
|
import java.awt.event.*;
|
|
import java.awt.geom.AffineTransform;
|
|
import java.awt.geom.NoninvertibleTransformException;
|
|
import java.awt.geom.Point2D;
|
|
import java.awt.image.BufferedImage;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
|
|
import static de.neemann.digital.draw.shapes.GenericShape.SIZE;
|
|
import static de.neemann.digital.draw.shapes.GenericShape.SIZE2;
|
|
import static java.awt.event.InputEvent.CTRL_DOWN_MASK;
|
|
|
|
/**
|
|
* Component which shows the circuit.
|
|
* ToDo: refactoring of repaint logic. Its to complex now.
|
|
*
|
|
* @author hneemann
|
|
*/
|
|
public class CircuitComponent extends JComponent implements Circuit.ChangedListener, LibraryListener {
|
|
/**
|
|
* 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 String DEL_ACTION = "myDelAction";
|
|
private static final String ESC_ACTION = "myEscAction";
|
|
private static final int MOUSE_BORDER_SMALL = 10;
|
|
private static final int MOUSE_BORDER_LARGE = 50;
|
|
|
|
private final Main parent;
|
|
private final ElementLibrary library;
|
|
private final HashSet<Drawable> highLighted;
|
|
private final ToolTipAction deleteAction;
|
|
|
|
private final MouseController mouseNormal;
|
|
private final MouseControllerInsertElement mouseInsertElement;
|
|
private final MouseControllerMoveElement mouseMoveElement;
|
|
private final MouseControllerMoveWire mouseMoveWire;
|
|
private final MouseControllerWire mouseWire;
|
|
private final MouseControllerSelect mouseSelect;
|
|
private final MouseControllerMoveSelected mouseMoveSelected;
|
|
private final MouseController mouseRun;
|
|
private final MouseControllerInsertCopied mouseInsertList;
|
|
private final Cursor moveCursor;
|
|
private final AbstractAction copyAction;
|
|
private final AbstractAction pasteAction;
|
|
private final AbstractAction rotateAction;
|
|
|
|
private Circuit circuit;
|
|
private MouseController activeMouseController;
|
|
private AffineTransform transform = new AffineTransform();
|
|
private Observer manualChangeObserver;
|
|
private Vector lastMousePos;
|
|
private Sync modelSync;
|
|
private boolean isManualScale;
|
|
private boolean hasChanged = true;
|
|
private boolean focusWasLost = false;
|
|
|
|
|
|
/**
|
|
* Creates a new instance
|
|
*
|
|
* @param parent the parent window
|
|
* @param library the library used to edit the attributes of the elements
|
|
* @param shapeFactory the shapeFactory used for copied elements
|
|
*/
|
|
public CircuitComponent(Main parent, ElementLibrary library, ShapeFactory shapeFactory) {
|
|
this.parent = parent;
|
|
this.library = library;
|
|
highLighted = new HashSet<>();
|
|
|
|
rotateAction = new AbstractAction(Lang.get("menu_rotate")) {
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
activeMouseController.rotate();
|
|
}
|
|
};
|
|
rotateAction.setEnabled(false);
|
|
|
|
|
|
copyAction = new AbstractAction(Lang.get("menu_copy")) {
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
if (activeMouseController instanceof MouseControllerSelect) {
|
|
MouseControllerSelect mcs = ((MouseControllerSelect) activeMouseController);
|
|
ArrayList<Movable> elements = circuit.getElementsToCopy(Vector.min(mcs.corner1, mcs.corner2), Vector.max(mcs.corner1, mcs.corner2), shapeFactory);
|
|
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
|
clipboard.setContents(new CircuitTransferable(elements), null);
|
|
removeHighLighted();
|
|
mouseNormal.activate();
|
|
}
|
|
}
|
|
};
|
|
copyAction.setEnabled(false);
|
|
|
|
pasteAction = new AbstractAction(Lang.get("menu_paste")) {
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
|
try {
|
|
Object data = clipboard.getData(DataFlavor.stringFlavor);
|
|
if (data instanceof String) {
|
|
Vector posVector = getPosVector(lastMousePos.x, lastMousePos.y);
|
|
ArrayList<Movable> elements = CircuitTransferable.createList(data, shapeFactory, posVector);
|
|
if (elements != null) {
|
|
removeHighLighted();
|
|
mouseInsertList.activate(elements, posVector);
|
|
}
|
|
}
|
|
} catch (Exception e1) {
|
|
e1.printStackTrace();
|
|
SwingUtilities.invokeLater(new ErrorMessage(Lang.get("msg_clipboardContainsNoImportableData")).setComponent(CircuitComponent.this));
|
|
}
|
|
}
|
|
};
|
|
|
|
deleteAction = new ToolTipAction(Lang.get("menu_delete"), ICON_DELETE) {
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
activeMouseController.delete();
|
|
}
|
|
}.setToolTip(Lang.get("menu_delete_tt"));
|
|
|
|
Action escapeAction = new AbstractAction() {
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
activeMouseController.escapePressed();
|
|
}
|
|
};
|
|
|
|
AbstractAction programAction = new AbstractAction(Lang.get("menu_programDiode")) {
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
if (activeMouseController instanceof MouseControllerNormal) {
|
|
programElementAt(getPosVector(lastMousePos.x, lastMousePos.y));
|
|
}
|
|
}
|
|
};
|
|
|
|
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), ESC_ACTION);
|
|
getActionMap().put(ESC_ACTION, escapeAction);
|
|
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), DEL_ACTION);
|
|
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), DEL_ACTION);
|
|
getActionMap().put(DEL_ACTION, deleteAction);
|
|
getInputMap().put(KeyStroke.getKeyStroke('C', Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "myCopy");
|
|
getActionMap().put("myCopy", copyAction);
|
|
getInputMap().put(KeyStroke.getKeyStroke('V', Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "myPaste");
|
|
getActionMap().put("myPaste", pasteAction);
|
|
getInputMap().put(KeyStroke.getKeyStroke("R"), "myRotate");
|
|
getActionMap().put("myRotate", rotateAction);
|
|
getInputMap().put(KeyStroke.getKeyStroke("P"), "myProgram");
|
|
getActionMap().put("myProgram", programAction);
|
|
|
|
setFocusable(true);
|
|
|
|
addMouseWheelListener(e -> {
|
|
Vector pos = getPosVector(e);
|
|
double f = Math.pow(0.9, e.getWheelRotation());
|
|
transform.translate(pos.x, pos.y);
|
|
transform.scale(f, f);
|
|
transform.translate(-pos.x, -pos.y);
|
|
isManualScale = true;
|
|
hasChanged();
|
|
});
|
|
|
|
addComponentListener(new ComponentAdapter() {
|
|
@Override
|
|
public void componentResized(ComponentEvent componentEvent) {
|
|
if (!isManualScale)
|
|
fitCircuit();
|
|
}
|
|
});
|
|
|
|
addFocusListener(new FocusAdapter() {
|
|
@Override
|
|
public void focusLost(FocusEvent focusEvent) {
|
|
focusWasLost = true;
|
|
}
|
|
});
|
|
|
|
Cursor normalCursor = new Cursor(Cursor.DEFAULT_CURSOR);
|
|
moveCursor = new Cursor(Cursor.MOVE_CURSOR);
|
|
mouseNormal = new MouseControllerNormal(normalCursor);
|
|
mouseInsertElement = new MouseControllerInsertElement(normalCursor);
|
|
mouseInsertList = new MouseControllerInsertCopied(normalCursor);
|
|
mouseMoveElement = new MouseControllerMoveElement(normalCursor);
|
|
mouseMoveWire = new MouseControllerMoveWire(normalCursor);
|
|
mouseWire = new MouseControllerWire(normalCursor);
|
|
mouseSelect = new MouseControllerSelect(new Cursor(Cursor.CROSSHAIR_CURSOR));
|
|
mouseMoveSelected = new MouseControllerMoveSelected(moveCursor);
|
|
mouseRun = new MouseControllerRun(normalCursor);
|
|
|
|
setCircuit(new Circuit());
|
|
|
|
MouseDispatcher dispatcher = new MouseDispatcher();
|
|
addMouseMotionListener(dispatcher);
|
|
addMouseListener(dispatcher);
|
|
|
|
setToolTipText("");
|
|
}
|
|
|
|
private void programElementAt(Vector pos) {
|
|
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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* invalidates the image buffer and calls repaint();
|
|
*/
|
|
public void hasChanged() {
|
|
hasChanged = true;
|
|
repaint();
|
|
}
|
|
|
|
/**
|
|
* @return the main frame
|
|
*/
|
|
public Main getMain() {
|
|
return parent;
|
|
}
|
|
|
|
@Override
|
|
public String getToolTipText(MouseEvent event) {
|
|
Vector pos = getPosVector(event);
|
|
VisualElement ve = circuit.getElementAt(pos);
|
|
if (ve == null) return null;
|
|
|
|
Pin p = circuit.getPinAt(raster(pos), ve);
|
|
if (p != null)
|
|
return createPinToolTip(p);
|
|
|
|
try {
|
|
ElementTypeDescription etd = library.getElementType(ve.getElementName());
|
|
return checkToolTip(etd.getDescription(ve.getElementAttributes()));
|
|
} catch (ElementNotFoundException e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private String createPinToolTip(Pin p) {
|
|
String text = p.getName();
|
|
final String des = p.getDescription();
|
|
if (des != null && des.length() > 0) {
|
|
text += ": " + des;
|
|
}
|
|
return checkToolTip(text);
|
|
}
|
|
|
|
private String checkToolTip(String tt) {
|
|
if (tt != null && tt.length() == 0)
|
|
return null;
|
|
else
|
|
return StringUtils.textToHTML(tt);
|
|
}
|
|
|
|
/**
|
|
* @return the delete action to put it to the toolbar
|
|
*/
|
|
public ToolTipAction getDeleteAction() {
|
|
return deleteAction;
|
|
}
|
|
|
|
/**
|
|
* @return the copy action
|
|
*/
|
|
public AbstractAction getCopyAction() {
|
|
return copyAction;
|
|
}
|
|
|
|
/**
|
|
* @return the paste action
|
|
*/
|
|
public AbstractAction getPasteAction() {
|
|
return pasteAction;
|
|
}
|
|
|
|
/**
|
|
* @return the rotate action
|
|
*/
|
|
public AbstractAction getRotateAction() {
|
|
return rotateAction;
|
|
}
|
|
|
|
/**
|
|
* Sets the observer to call if the user is clicking on elements while running.
|
|
*
|
|
* @param callOnManualChange the listener
|
|
*/
|
|
public void setManualChangeObserver(Observer callOnManualChange) {
|
|
this.manualChangeObserver = callOnManualChange;
|
|
}
|
|
|
|
/**
|
|
* Sets the edit mode and resets the circuit
|
|
*
|
|
* @param runMode true if running, false if editing
|
|
* @param modelSync used to access the running model
|
|
*/
|
|
public void setModeAndReset(boolean runMode, Sync modelSync) {
|
|
this.modelSync = modelSync;
|
|
if (runMode)
|
|
mouseRun.activate();
|
|
else {
|
|
mouseNormal.activate();
|
|
circuit.clearState();
|
|
}
|
|
requestFocusInWindow();
|
|
}
|
|
|
|
/**
|
|
* @return the high lighted elements
|
|
*/
|
|
public Collection<Drawable> getHighLighted() {
|
|
return highLighted;
|
|
}
|
|
|
|
/**
|
|
* Adds a drawable to the highlighted list
|
|
*
|
|
* @param drawable the drawable to add
|
|
* @param <T> type of drawable
|
|
*/
|
|
public <T extends Drawable> void addHighLighted(T drawable) {
|
|
if (drawable != null)
|
|
highLighted.add(drawable);
|
|
}
|
|
|
|
/**
|
|
* Add a list of drawables to high light
|
|
*
|
|
* @param drawables the list of drawables
|
|
*/
|
|
public void addHighLighted(Collection<? extends Drawable> drawables) {
|
|
if (drawables != null)
|
|
highLighted.addAll(drawables);
|
|
}
|
|
|
|
/**
|
|
* Adds all the wires representing the given value to the highlighted list
|
|
*
|
|
* @param values the value
|
|
*/
|
|
public void addHighLightedWires(ImmutableList<ObservableValue> values) {
|
|
if (values == null) return;
|
|
|
|
HashSet<ObservableValue> ov = new HashSet<>();
|
|
ov.addAll(values);
|
|
for (Wire w : circuit.getWires())
|
|
if (ov.contains(w.getValue()))
|
|
addHighLighted(w);
|
|
}
|
|
|
|
/**
|
|
* remove all highlighted elements
|
|
*/
|
|
public void removeHighLighted() {
|
|
highLighted.clear();
|
|
}
|
|
|
|
/**
|
|
* Adds the given element to insert to the circuit
|
|
*
|
|
* @param element the element to insert
|
|
*/
|
|
public void setPartToInsert(VisualElement element) {
|
|
parent.stopModel();
|
|
mouseInsertElement.activate(element);
|
|
Point point = MouseInfo.getPointerInfo().getLocation();
|
|
SwingUtilities.convertPointFromScreen(point, this);
|
|
if (point.x < MOUSE_BORDER_LARGE || point.x > getWidth() - MOUSE_BORDER_SMALL
|
|
|| point.y < MOUSE_BORDER_LARGE || point.y > getHeight() - MOUSE_BORDER_SMALL) {
|
|
|
|
if (point.x < MOUSE_BORDER_LARGE)
|
|
point.x = MOUSE_BORDER_LARGE;
|
|
else if (point.x > getWidth() - MOUSE_BORDER_SMALL)
|
|
point.x = getWidth() - MOUSE_BORDER_SMALL;
|
|
|
|
if (point.y < MOUSE_BORDER_LARGE)
|
|
point.y = MOUSE_BORDER_LARGE;
|
|
else if (point.y > getHeight() - MOUSE_BORDER_SMALL)
|
|
point.y = getHeight() - MOUSE_BORDER_SMALL;
|
|
|
|
SwingUtilities.convertPointToScreen(point, this);
|
|
|
|
try {
|
|
new Robot().mouseMove(point.x, point.y);
|
|
} catch (AWTException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private BufferedImage buffer;
|
|
private int highlightedPaintedSize;
|
|
|
|
@Override
|
|
protected void paintComponent(Graphics g) {
|
|
super.paintComponent(g);
|
|
|
|
boolean needsNewBuffer = buffer == null
|
|
|| getWidth() != buffer.getWidth()
|
|
|| getHeight() != buffer.getHeight();
|
|
|
|
if (needsNewBuffer && !isManualScale)
|
|
fitCircuit();
|
|
|
|
if (hasChanged
|
|
|| needsNewBuffer
|
|
|| highLighted.size() != highlightedPaintedSize) {
|
|
|
|
// long time = System.currentTimeMillis();
|
|
if (needsNewBuffer)
|
|
buffer = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().createCompatibleImage(getWidth(), getHeight());
|
|
|
|
Graphics2D gr2 = buffer.createGraphics();
|
|
gr2.setColor(Color.WHITE);
|
|
gr2.fillRect(0, 0, getWidth(), getHeight());
|
|
gr2.transform(transform);
|
|
|
|
GraphicSwing gr = new GraphicSwing(gr2, (int) (2 / transform.getScaleX()));
|
|
circuit.drawTo(gr, highLighted, modelSync);
|
|
highlightedPaintedSize = highLighted.size();
|
|
hasChanged = false;
|
|
// System.out.println("repaint: " + Long.toString(System.currentTimeMillis() - time) + "ms");
|
|
}
|
|
|
|
g.drawImage(buffer, 0, 0, null);
|
|
|
|
Graphics2D gr2 = (Graphics2D) g;
|
|
AffineTransform oldTrans = gr2.getTransform();
|
|
gr2.transform(transform);
|
|
GraphicSwing gr = new GraphicSwing(gr2, (int) (2 / transform.getScaleX()));
|
|
activeMouseController.drawTo(gr);
|
|
gr2.setTransform(oldTrans);
|
|
}
|
|
|
|
@Override
|
|
public void circuitHasChanged() {
|
|
hasChanged = true;
|
|
}
|
|
|
|
/**
|
|
* forces a immediately repaint
|
|
* Is called from {@link de.neemann.digital.gui.GuiModelObserver} if the models data has changed.
|
|
* Therefore the double buffer is invalidated.
|
|
*/
|
|
public void paintImmediately() {
|
|
hasChanged = true;
|
|
paintImmediately(0, 0, getWidth(), getHeight());
|
|
}
|
|
|
|
|
|
private Vector getPosVector(MouseEvent e) {
|
|
return getPosVector(e.getX(), e.getY());
|
|
}
|
|
|
|
private Vector getPosVector(int x, int y) {
|
|
try {
|
|
Point2D.Double p = new Point2D.Double();
|
|
transform.inverseTransform(new Point(x, y), p);
|
|
return new Vector((int) Math.round(p.getX()), (int) Math.round(p.getY()));
|
|
} catch (NoninvertibleTransformException e1) {
|
|
throw new RuntimeException(e1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* rounds the given vector to the raster
|
|
*
|
|
* @param pos the vector
|
|
* @return pos round to raster
|
|
*/
|
|
static Vector raster(Vector pos) {
|
|
return new Vector((int) Math.round((double) pos.x / SIZE) * SIZE,
|
|
(int) Math.round((double) pos.y / SIZE) * SIZE);
|
|
}
|
|
|
|
/**
|
|
* @return the circuit shown
|
|
*/
|
|
public Circuit getCircuit() {
|
|
return circuit;
|
|
}
|
|
|
|
/**
|
|
* Sets a circuit to this component
|
|
*
|
|
* @param circuit the circuit
|
|
*/
|
|
public void setCircuit(Circuit circuit) {
|
|
|
|
if (this.circuit != null) {
|
|
this.circuit.removeListener(this);
|
|
}
|
|
|
|
this.circuit = circuit;
|
|
|
|
circuit.addListener(this);
|
|
|
|
fitCircuit();
|
|
setModeAndReset(false, NoSync.INST);
|
|
}
|
|
|
|
/**
|
|
* maximizes the circuit shown
|
|
*/
|
|
public void fitCircuit() {
|
|
GraphicMinMax gr = new GraphicMinMax();
|
|
circuit.drawTo(gr);
|
|
|
|
AffineTransform newTrans = new AffineTransform();
|
|
if (gr.getMin() != null && getWidth() != 0 && getHeight() != 0) {
|
|
Vector delta = gr.getMax().sub(gr.getMin());
|
|
double sx = ((double) getWidth()) / (delta.x + Style.NORMAL.getThickness() * 2);
|
|
double sy = ((double) getHeight()) / (delta.y + Style.NORMAL.getThickness() * 2);
|
|
double s = Math.min(sx, sy);
|
|
|
|
|
|
newTrans.setToScale(s, s); // set Scaling
|
|
|
|
Vector center = gr.getMin().add(gr.getMax()).div(2);
|
|
newTrans.translate(-center.x, -center.y); // move drawing center to (0,0)
|
|
|
|
Vector dif = new Vector(getWidth(), getHeight()).div(2);
|
|
newTrans.translate(dif.x / s, dif.y / s); // move drawing center to frame center
|
|
isManualScale = false;
|
|
} else {
|
|
isManualScale = true;
|
|
}
|
|
if (!newTrans.equals(transform)) {
|
|
transform = newTrans;
|
|
hasChanged();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* scales the circuit
|
|
*
|
|
* @param f factor to scale
|
|
*/
|
|
public void scaleCircuit(double f) {
|
|
Vector dif = getPosVector(getWidth() / 2, getHeight() / 2);
|
|
transform.translate(dif.x, dif.y);
|
|
transform.scale(f, f);
|
|
transform.translate(-dif.x, -dif.y);
|
|
isManualScale = true;
|
|
hasChanged();
|
|
}
|
|
|
|
private void editAttributes(VisualElement vp, MouseEvent e) {
|
|
String name = vp.getElementName();
|
|
try {
|
|
ElementTypeDescription elementType = library.getElementType(name);
|
|
ArrayList<Key> list = elementType.getAttributeList();
|
|
if (list.size() > 0) {
|
|
Point p = new Point(e.getX(), e.getY());
|
|
SwingUtilities.convertPointToScreen(p, CircuitComponent.this);
|
|
AttributeDialog attributeDialog = new AttributeDialog(this, p, list, vp.getElementAttributes());
|
|
if (elementType instanceof ElementLibrary.ElementTypeDescriptionCustom) {
|
|
attributeDialog.addButton(Lang.get("attr_openCircuitLabel"), new ToolTipAction(Lang.get("attr_openCircuit")) {
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
attributeDialog.dispose();
|
|
new Main.MainBuilder()
|
|
.setParent(CircuitComponent.this)
|
|
.setFileToOpen(((ElementLibrary.ElementTypeDescriptionCustom) elementType).getFile())
|
|
.setLibrary(library)
|
|
.denyMostFileActions()
|
|
.openLater();
|
|
}
|
|
}.setToolTip(Lang.get("attr_openCircuit_tt")));
|
|
}
|
|
attributeDialog.addButton(new ToolTipAction(Lang.get("attr_help")) {
|
|
@Override
|
|
public void actionPerformed(ActionEvent actionEvent) {
|
|
try {
|
|
new ElementHelpDialog(attributeDialog, elementType, vp.getElementAttributes()).setVisible(true);
|
|
} catch (PinException | NodeException e1) {
|
|
new ErrorMessage(Lang.get("msg_creatingHelp")).addCause(e1).show(CircuitComponent.this);
|
|
}
|
|
}
|
|
}.setToolTip(Lang.get("attr_help_tt")));
|
|
if (attributeDialog.showDialog()) {
|
|
circuit.modified();
|
|
hasChanged();
|
|
}
|
|
}
|
|
} catch (ElementNotFoundException ex) {
|
|
// do nothing if element not found!
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void libraryChanged(LibraryNode node) {
|
|
circuit.clearState();
|
|
hasChanged = true;
|
|
repaint();
|
|
}
|
|
|
|
private class MouseDispatcher extends MouseAdapter implements MouseMotionListener {
|
|
private Vector pos;
|
|
private boolean isMoved;
|
|
|
|
@Override
|
|
public void mousePressed(MouseEvent e) {
|
|
pos = new Vector(e.getX(), e.getY());
|
|
isMoved = false;
|
|
requestFocusInWindow();
|
|
activeMouseController.pressed(e);
|
|
}
|
|
|
|
@Override
|
|
public void mouseReleased(MouseEvent e) {
|
|
activeMouseController.released(e);
|
|
if (!(wasMoved(e) || isMoved))
|
|
activeMouseController.clicked(e);
|
|
}
|
|
|
|
private boolean wasMoved(MouseEvent e) {
|
|
Vector d = new Vector(e.getX(), e.getY()).sub(pos);
|
|
return Math.abs(d.x) > SIZE2 || Math.abs(d.y) > SIZE2;
|
|
}
|
|
|
|
@Override
|
|
public void mouseMoved(MouseEvent e) {
|
|
lastMousePos = new Vector(e.getX(), e.getY());
|
|
activeMouseController.moved(e);
|
|
}
|
|
|
|
@Override
|
|
public void mouseEntered(MouseEvent e) {
|
|
lastMousePos = new Vector(e.getX(), e.getY());
|
|
activeMouseController.moved(e);
|
|
}
|
|
|
|
@Override
|
|
public void mouseDragged(MouseEvent e) {
|
|
if (wasMoved(e) || isMoved) {
|
|
isMoved = true;
|
|
if (!activeMouseController.dragged(e)) {
|
|
Vector newPos = new Vector(e.getX(), e.getY());
|
|
Vector delta = newPos.sub(pos);
|
|
double s = transform.getScaleX();
|
|
transform.translate(delta.x / s, delta.y / s);
|
|
pos = newPos;
|
|
isManualScale = true;
|
|
hasChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//MouseController can not be final because its overridden. Maybe checkstyle has a bug?
|
|
//CHECKSTYLE.OFF: FinalClass
|
|
private class MouseController {
|
|
private final Cursor mouseCursor;
|
|
|
|
private MouseController(Cursor mouseCursor) {
|
|
this.mouseCursor = mouseCursor;
|
|
}
|
|
|
|
void activate() {
|
|
activeMouseController = this;
|
|
deleteAction.setActive(false);
|
|
copyAction.setEnabled(false);
|
|
rotateAction.setEnabled(false);
|
|
setCursor(mouseCursor);
|
|
hasChanged();
|
|
}
|
|
|
|
void clicked(MouseEvent e) {
|
|
}
|
|
|
|
void pressed(MouseEvent e) {
|
|
}
|
|
|
|
void released(MouseEvent e) {
|
|
}
|
|
|
|
void moved(MouseEvent e) {
|
|
}
|
|
|
|
boolean dragged(MouseEvent e) {
|
|
return false;
|
|
}
|
|
|
|
public void drawTo(Graphic gr) {
|
|
}
|
|
|
|
public void delete() {
|
|
}
|
|
|
|
public void rotate() {
|
|
}
|
|
|
|
public void escapePressed() {
|
|
}
|
|
}
|
|
//CHECKSTYLE.ON: FinalClass
|
|
|
|
private final class MouseControllerNormal extends MouseController {
|
|
private Vector pos;
|
|
private int downButton;
|
|
|
|
private MouseControllerNormal(Cursor cursor) {
|
|
super(cursor);
|
|
}
|
|
|
|
private VisualElement getVisualElement(Vector pos, boolean includeText) {
|
|
VisualElement vp = null;
|
|
List<VisualElement> list = circuit.getElementListAt(pos, includeText);
|
|
if (list.size() == 1)
|
|
vp = list.get(0);
|
|
else if (list.size() > 1) {
|
|
ItemPicker<VisualElement> picker = new ItemPicker<>(CircuitComponent.this, list);
|
|
vp = picker.select();
|
|
}
|
|
return vp;
|
|
}
|
|
|
|
@Override
|
|
void clicked(MouseEvent e) {
|
|
Vector pos = getPosVector(e);
|
|
|
|
if (e.getButton() == MouseEvent.BUTTON3) {
|
|
VisualElement vp = getVisualElement(pos, true);
|
|
if (vp != null)
|
|
editAttributes(vp, e);
|
|
} else if (e.getButton() == MouseEvent.BUTTON1) {
|
|
VisualElement vp = getVisualElement(pos, false);
|
|
if (vp != null) {
|
|
if (circuit.isPinPos(raster(pos), vp) && !e.isControlDown())
|
|
mouseWire.activate(pos);
|
|
else
|
|
mouseMoveElement.activate(vp, pos);
|
|
} else {
|
|
if (e.isControlDown()) {
|
|
Wire wire = circuit.getWireAt(pos, SIZE2);
|
|
if (wire != null)
|
|
mouseMoveWire.activate(wire, pos);
|
|
} else if (!focusWasLost)
|
|
mouseWire.activate(pos);
|
|
}
|
|
}
|
|
focusWasLost = false;
|
|
}
|
|
|
|
@Override
|
|
void pressed(MouseEvent e) {
|
|
downButton = e.getButton();
|
|
pos = getPosVector(e);
|
|
}
|
|
|
|
@Override
|
|
boolean dragged(MouseEvent e) {
|
|
if (downButton == MouseEvent.BUTTON1) {
|
|
mouseSelect.activate(pos, getPosVector(e));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
private final class MouseControllerInsertElement extends MouseController {
|
|
private VisualElement element;
|
|
private Vector delta;
|
|
|
|
private MouseControllerInsertElement(Cursor cursor) {
|
|
super(cursor);
|
|
}
|
|
|
|
private void activate(VisualElement element) {
|
|
super.activate();
|
|
this.element = element;
|
|
delta = null;
|
|
deleteAction.setActive(true);
|
|
rotateAction.setEnabled(true);
|
|
}
|
|
|
|
@Override
|
|
void moved(MouseEvent e) {
|
|
if (delta == null) {
|
|
GraphicMinMax minMax = element.getMinMax(false);
|
|
delta = element.getPos().sub(minMax.getMax());
|
|
}
|
|
element.setPos(raster(getPosVector(e).add(delta)));
|
|
repaint();
|
|
}
|
|
|
|
@Override
|
|
public void delete() {
|
|
mouseNormal.activate();
|
|
}
|
|
|
|
@Override
|
|
public void drawTo(Graphic gr) {
|
|
if (delta != null)
|
|
element.drawTo(gr, true);
|
|
}
|
|
|
|
@Override
|
|
void clicked(MouseEvent e) {
|
|
if (e.getButton() == MouseEvent.BUTTON1) {
|
|
circuit.add(element);
|
|
hasChanged();
|
|
}
|
|
mouseNormal.activate();
|
|
focusWasLost = false;
|
|
}
|
|
|
|
@Override
|
|
public void rotate() {
|
|
element.rotate();
|
|
repaint();
|
|
}
|
|
|
|
@Override
|
|
public void escapePressed() {
|
|
mouseNormal.activate();
|
|
}
|
|
}
|
|
|
|
private final class MouseControllerMoveElement extends MouseController {
|
|
private VisualElement visualElement;
|
|
private Vector delta;
|
|
private Vector initialPos;
|
|
private int initialRot;
|
|
|
|
private MouseControllerMoveElement(Cursor cursor) {
|
|
super(cursor);
|
|
}
|
|
|
|
private void activate(VisualElement visualElement, Vector pos) {
|
|
super.activate();
|
|
this.visualElement = visualElement;
|
|
initialPos = visualElement.getPos();
|
|
initialRot = visualElement.getRotate();
|
|
delta = initialPos.sub(pos);
|
|
deleteAction.setActive(true);
|
|
rotateAction.setEnabled(true);
|
|
hasChanged();
|
|
}
|
|
|
|
@Override
|
|
void clicked(MouseEvent e) {
|
|
visualElement.setPos(raster(visualElement.getPos()));
|
|
mouseNormal.activate();
|
|
}
|
|
|
|
@Override
|
|
void moved(MouseEvent e) {
|
|
Vector pos = getPosVector(e);
|
|
visualElement.setPos(raster(pos.add(delta)));
|
|
circuit.modified();
|
|
hasChanged();
|
|
}
|
|
|
|
@Override
|
|
public void drawTo(Graphic gr) {
|
|
visualElement.drawTo(gr, true);
|
|
}
|
|
|
|
@Override
|
|
public void delete() {
|
|
circuit.delete(visualElement);
|
|
mouseNormal.activate();
|
|
isManualScale = true;
|
|
}
|
|
|
|
@Override
|
|
public void rotate() {
|
|
visualElement.rotate();
|
|
circuit.modified();
|
|
hasChanged();
|
|
}
|
|
|
|
@Override
|
|
public void escapePressed() {
|
|
visualElement.setPos(raster(initialPos));
|
|
visualElement.setRotation(initialRot);
|
|
mouseNormal.activate();
|
|
}
|
|
}
|
|
|
|
private final class MouseControllerMoveWire extends MouseController {
|
|
private Wire wire;
|
|
private Vector pos;
|
|
private Vector initialPos;
|
|
|
|
private MouseControllerMoveWire(Cursor cursor) {
|
|
super(cursor);
|
|
}
|
|
|
|
private void activate(Wire wire, Vector pos) {
|
|
super.activate();
|
|
this.wire = wire;
|
|
this.pos = raster(pos);
|
|
this.initialPos = this.pos;
|
|
deleteAction.setActive(true);
|
|
removeHighLighted();
|
|
hasChanged();
|
|
}
|
|
|
|
@Override
|
|
void clicked(MouseEvent e) {
|
|
removeHighLighted();
|
|
circuit.elementsMoved();
|
|
mouseNormal.activate();
|
|
}
|
|
|
|
@Override
|
|
void moved(MouseEvent e) {
|
|
Vector pos = raster(getPosVector(e));
|
|
final Vector delta = pos.sub(this.pos);
|
|
if (!delta.isZero()) {
|
|
wire.move(delta);
|
|
wire.noDot();
|
|
isManualScale = true;
|
|
circuit.modified();
|
|
hasChanged();
|
|
}
|
|
this.pos = pos;
|
|
}
|
|
|
|
@Override
|
|
public void delete() {
|
|
circuit.delete(wire);
|
|
mouseNormal.activate();
|
|
isManualScale = true;
|
|
}
|
|
|
|
@Override
|
|
public void drawTo(Graphic gr) {
|
|
// ensure that highlighted wire is visible by drawing it on top of other drawings.
|
|
wire.drawTo(gr, true);
|
|
}
|
|
|
|
@Override
|
|
public void escapePressed() {
|
|
wire.move(initialPos.sub(pos));
|
|
removeHighLighted();
|
|
circuit.elementsMoved();
|
|
mouseNormal.activate();
|
|
}
|
|
}
|
|
|
|
|
|
private final class MouseControllerWire extends MouseController {
|
|
private Wire wire;
|
|
|
|
private MouseControllerWire(Cursor cursor) {
|
|
super(cursor);
|
|
}
|
|
|
|
private void activate(Vector startPos) {
|
|
super.activate();
|
|
Vector pos = raster(startPos);
|
|
wire = new Wire(pos, pos);
|
|
}
|
|
|
|
@Override
|
|
void moved(MouseEvent e) {
|
|
wire.setP2(raster(getPosVector(e)));
|
|
repaint();
|
|
}
|
|
|
|
@Override
|
|
void clicked(MouseEvent e) {
|
|
if (e.getButton() == MouseEvent.BUTTON3)
|
|
mouseNormal.activate();
|
|
else {
|
|
circuit.add(wire);
|
|
if (circuit.isPinPos(wire.p2))
|
|
mouseNormal.activate();
|
|
else
|
|
wire = new Wire(wire.p2, wire.p2);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void drawTo(Graphic gr) {
|
|
wire.drawTo(gr, false);
|
|
}
|
|
|
|
@Override
|
|
public void escapePressed() {
|
|
mouseNormal.activate();
|
|
}
|
|
}
|
|
|
|
private final class MouseControllerSelect extends MouseController {
|
|
private static final int MIN_SIZE = 8;
|
|
private Vector corner1;
|
|
private Vector corner2;
|
|
private boolean wasReleased;
|
|
|
|
private MouseControllerSelect(Cursor cursor) {
|
|
super(cursor);
|
|
}
|
|
|
|
private void activate(Vector corner1, Vector corner2) {
|
|
super.activate();
|
|
this.corner1 = corner1;
|
|
this.corner2 = corner2;
|
|
deleteAction.setActive(true);
|
|
copyAction.setEnabled(true);
|
|
rotateAction.setEnabled(true);
|
|
wasReleased = false;
|
|
}
|
|
|
|
@Override
|
|
void clicked(MouseEvent e) {
|
|
mouseNormal.activate();
|
|
removeHighLighted();
|
|
}
|
|
|
|
@Override
|
|
void released(MouseEvent e) {
|
|
wasReleased = true;
|
|
Vector dif = corner1.sub(corner2);
|
|
if (Math.abs(dif.x) > MIN_SIZE && Math.abs(dif.y) > MIN_SIZE)
|
|
setCursor(moveCursor);
|
|
else {
|
|
removeHighLighted();
|
|
mouseNormal.activate();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
boolean dragged(MouseEvent e) {
|
|
if (wasReleased) {
|
|
mouseMoveSelected.activate(corner1, corner2, getPosVector(e));
|
|
} else {
|
|
corner2 = getPosVector(e);
|
|
if ((e.getModifiersEx() & CTRL_DOWN_MASK) != 0) {
|
|
Vector dif = corner2.sub(corner1);
|
|
int dx = dif.x;
|
|
int dy = dif.y;
|
|
int absDx = Math.abs(dx);
|
|
int absDy = Math.abs(dy);
|
|
if (absDx != absDy) {
|
|
if (absDx > absDy) {
|
|
if (dx > absDy) dx = absDy;
|
|
else dx = -absDy;
|
|
} else {
|
|
if (dy > absDx) dy = absDx;
|
|
else dy = -absDx;
|
|
}
|
|
}
|
|
corner2 = corner1.add(dx, dy);
|
|
}
|
|
|
|
ArrayList<Drawable> elements = circuit.getElementsToHighlight(Vector.min(corner1, corner2), Vector.max(corner1, corner2));
|
|
removeHighLighted();
|
|
if (elements != null)
|
|
addHighLighted(elements);
|
|
|
|
repaint();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void drawTo(Graphic gr) {
|
|
Vector p1 = new Vector(corner1.x, corner2.y);
|
|
Vector p2 = new Vector(corner2.x, corner1.y);
|
|
gr.drawLine(corner1, p1, Style.DASH);
|
|
gr.drawLine(p1, corner2, Style.DASH);
|
|
gr.drawLine(p2, corner2, Style.DASH);
|
|
gr.drawLine(corner1, p2, Style.DASH);
|
|
}
|
|
|
|
@Override
|
|
public void delete() {
|
|
circuit.delete(Vector.min(corner1, corner2), Vector.max(corner1, corner2));
|
|
mouseNormal.activate();
|
|
isManualScale = true;
|
|
}
|
|
|
|
public void rotate() {
|
|
mouseMoveSelected.activate(corner1, corner2, lastMousePos);
|
|
mouseMoveSelected.rotate();
|
|
}
|
|
|
|
@Override
|
|
public void escapePressed() {
|
|
removeHighLighted();
|
|
mouseNormal.activate();
|
|
}
|
|
}
|
|
|
|
private void rotateElements(ArrayList<Movable> elements, Vector pos) {
|
|
Vector p1 = raster(pos);
|
|
|
|
Transform transform = new TransformRotate(p1, 1) {
|
|
@Override
|
|
public Vector transform(Vector v) {
|
|
return super.transform(v.sub(p1));
|
|
}
|
|
};
|
|
|
|
for (Movable m : elements) {
|
|
|
|
if (m instanceof VisualElement) {
|
|
VisualElement ve = (VisualElement) m;
|
|
ve.rotate();
|
|
ve.setPos(transform.transform(ve.getPos()));
|
|
} else if (m instanceof Wire) {
|
|
Wire w = (Wire) m;
|
|
w.p1 = transform.transform(w.p1);
|
|
w.p2 = transform.transform(w.p2);
|
|
} else {
|
|
Vector p = m.getPos();
|
|
Vector t = transform.transform(p);
|
|
m.move(t.sub(p));
|
|
}
|
|
|
|
}
|
|
|
|
circuit.modified();
|
|
hasChanged();
|
|
}
|
|
|
|
|
|
private final class MouseControllerMoveSelected extends MouseController {
|
|
private ArrayList<Movable> elements;
|
|
private Vector lastPos;
|
|
private Vector center;
|
|
private boolean wasMoved;
|
|
|
|
private MouseControllerMoveSelected(Cursor cursor) {
|
|
super(cursor);
|
|
}
|
|
|
|
private void activate(Vector corner1, Vector corner2, Vector pos) {
|
|
super.activate();
|
|
rotateAction.setEnabled(true);
|
|
lastPos = pos;
|
|
center = corner1.add(corner2).div(2);
|
|
wasMoved = false;
|
|
elements = circuit.getElementsToMove(Vector.min(corner1, corner2), Vector.max(corner1, corner2));
|
|
}
|
|
|
|
@Override
|
|
void moved(MouseEvent e) {
|
|
lastPos = getPosVector(e);
|
|
}
|
|
|
|
@Override
|
|
boolean dragged(MouseEvent e) {
|
|
if (elements != null) {
|
|
Vector pos = getPosVector(e);
|
|
Vector delta = raster(pos.sub(lastPos));
|
|
|
|
if (delta.x != 0 || delta.y != 0) {
|
|
for (Movable m : elements)
|
|
m.move(delta);
|
|
wasMoved = true;
|
|
|
|
hasChanged();
|
|
lastPos = lastPos.add(delta);
|
|
center = center.add(delta);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
void released(MouseEvent e) {
|
|
if (wasMoved)
|
|
circuit.elementsMoved();
|
|
removeHighLighted();
|
|
mouseNormal.activate();
|
|
}
|
|
|
|
@Override
|
|
public void rotate() {
|
|
rotateElements(elements, center);
|
|
}
|
|
}
|
|
|
|
private final class MouseControllerInsertCopied extends MouseController {
|
|
private ArrayList<Movable> elements;
|
|
private Vector lastPos;
|
|
|
|
private MouseControllerInsertCopied(Cursor cursor) {
|
|
super(cursor);
|
|
}
|
|
|
|
private void activate(ArrayList<Movable> elements, Vector pos) {
|
|
super.activate();
|
|
this.elements = elements;
|
|
lastPos = pos;
|
|
deleteAction.setActive(true);
|
|
rotateAction.setEnabled(true);
|
|
}
|
|
|
|
@Override
|
|
void moved(MouseEvent e) {
|
|
if (elements != null) {
|
|
Vector pos = getPosVector(e);
|
|
Vector delta = raster(pos.sub(lastPos));
|
|
|
|
if (delta.x != 0 || delta.y != 0) {
|
|
for (Movable m : elements)
|
|
m.move(delta);
|
|
|
|
repaint();
|
|
lastPos = lastPos.add(delta);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void drawTo(Graphic gr) {
|
|
if (elements != null)
|
|
for (Movable m : elements)
|
|
if (m instanceof Drawable)
|
|
((Drawable) m).drawTo(gr, true);
|
|
}
|
|
|
|
@Override
|
|
public void delete() {
|
|
mouseNormal.activate();
|
|
}
|
|
|
|
@Override
|
|
void clicked(MouseEvent e) {
|
|
if (elements != null && e.getButton() == 1) {
|
|
for (Movable m : elements) {
|
|
if (m instanceof Wire)
|
|
circuit.add((Wire) m);
|
|
if (m instanceof VisualElement)
|
|
circuit.add((VisualElement) m);
|
|
}
|
|
}
|
|
mouseNormal.activate();
|
|
focusWasLost = false;
|
|
}
|
|
|
|
@Override
|
|
public void rotate() {
|
|
rotateElements(elements, lastPos);
|
|
}
|
|
|
|
@Override
|
|
public void escapePressed() {
|
|
mouseNormal.activate();
|
|
}
|
|
}
|
|
|
|
|
|
private interface Actor {
|
|
boolean interact(CircuitComponent cc, Point p, Vector posInComponent, Sync modelSync);
|
|
}
|
|
|
|
private final class MouseControllerRun extends MouseController {
|
|
|
|
private boolean dragHandled;
|
|
|
|
private MouseControllerRun(Cursor cursor) {
|
|
super(cursor);
|
|
}
|
|
|
|
@Override
|
|
void pressed(MouseEvent e) {
|
|
VisualElement ve = getInteractiveElementAt(e);
|
|
if (ve != null) {
|
|
interact(e, ve::elementPressed);
|
|
dragHandled = true;
|
|
} else
|
|
dragHandled = false;
|
|
}
|
|
|
|
private VisualElement getInteractiveElementAt(MouseEvent e) {
|
|
List<VisualElement> elementList = circuit.getElementListAt(getPosVector(e), false);
|
|
for (VisualElement ve : elementList) {
|
|
if (ve.isInteractive())
|
|
return ve;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
void released(MouseEvent e) {
|
|
VisualElement ve = getInteractiveElementAt(e);
|
|
if (ve != null)
|
|
interact(e, ve::elementReleased);
|
|
}
|
|
|
|
@Override
|
|
void clicked(MouseEvent e) {
|
|
VisualElement ve = getInteractiveElementAt(e);
|
|
if (ve != null)
|
|
interact(e, ve::elementClicked);
|
|
}
|
|
|
|
@Override
|
|
boolean dragged(MouseEvent e) {
|
|
VisualElement ve = getInteractiveElementAt(e);
|
|
if (ve != null)
|
|
interact(e, ve::elementDragged);
|
|
return dragHandled;
|
|
}
|
|
|
|
private void interact(MouseEvent e, Actor actor) {
|
|
Point p = new Point(e.getX(), e.getY());
|
|
SwingUtilities.convertPointToScreen(p, CircuitComponent.this);
|
|
boolean modelHasChanged = actor.interact(CircuitComponent.this, p, getPosVector(e), modelSync);
|
|
if (modelHasChanged) {
|
|
if (manualChangeObserver != null)
|
|
manualChangeObserver.hasChanged();
|
|
} else
|
|
hasChanged();
|
|
}
|
|
}
|
|
|
|
}
|