diff --git a/src/main/java/de/neemann/digital/draw/graphics/VectorFloat.java b/src/main/java/de/neemann/digital/draw/graphics/VectorFloat.java index 39dedb714..220b26410 100644 --- a/src/main/java/de/neemann/digital/draw/graphics/VectorFloat.java +++ b/src/main/java/de/neemann/digital/draw/graphics/VectorFloat.java @@ -146,4 +146,13 @@ public class VectorFloat implements VectorInterface { public VectorFloat toFloat() { return this; } + + /** + * Creates vector which is orthogonal to this one. + * + * @return the orthogonal vector + */ + public VectorFloat getOrthogonal() { + return new VectorFloat(y, -x); + } } diff --git a/src/main/java/de/neemann/digital/fsm/FSM.java b/src/main/java/de/neemann/digital/fsm/FSM.java index 5fa8332bb..0e144f7fa 100644 --- a/src/main/java/de/neemann/digital/fsm/FSM.java +++ b/src/main/java/de/neemann/digital/fsm/FSM.java @@ -5,6 +5,9 @@ */ package de.neemann.digital.fsm; +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.PrettyPrintWriter; +import com.thoughtworks.xstream.io.xml.StaxDriver; import de.neemann.digital.analyse.TruthTable; import de.neemann.digital.analyse.expression.ExpressionException; import de.neemann.digital.draw.graphics.Graphic; @@ -12,6 +15,7 @@ import de.neemann.digital.draw.graphics.Vector; import de.neemann.digital.draw.graphics.VectorFloat; import de.neemann.digital.lang.Lang; +import java.io.*; import java.util.ArrayList; import java.util.List; @@ -23,6 +27,89 @@ public class FSM { private ArrayList states; private ArrayList transitions; private transient boolean initChecked; + private transient boolean modified; + private transient ModifiedListener modifiedListener; + + /** + * Creates a proper configured XStream instance + * + * @return the XStream instance + */ + public static XStream getxStream() { + XStream xStream = new XStream(new StaxDriver()); + xStream.alias("fsm", FSM.class); + xStream.alias("state", State.class); + xStream.alias("transition", Transition.class); + xStream.alias("vector", Vector.class); + xStream.aliasAttribute(Vector.class, "x", "x"); + xStream.aliasAttribute(Vector.class, "y", "y"); + xStream.alias("vectorf", VectorFloat.class); + xStream.aliasAttribute(VectorFloat.class, "x", "x"); + xStream.aliasAttribute(VectorFloat.class, "y", "y"); + return xStream; + } + + /** + * Creates a new circuit instance from a stored file + * + * @param filename filename + * @return the fsm + * @throws IOException IOException + */ + public static FSM loadFSM(File filename) throws IOException { + return loadFSM(new FileInputStream(filename)); + } + + /** + * Creates a new fsm instance from a stored file + * + * @param in the input stream + * @return the fsm + * @throws IOException IOException + */ + public static FSM loadFSM(InputStream in) throws IOException { + try { + XStream xStream = getxStream(); + final FSM fsm = (FSM) xStream.fromXML(in); + for (Transition t : fsm.transitions) + t.setFSM(fsm); + for (State s : fsm.states) + s.setFSM(fsm); + fsm.modified = false; + return fsm; + } catch (RuntimeException e) { + throw new IOException(Lang.get("err_invalidFileFormat"), e); + } finally { + in.close(); + } + } + + /** + * Stores the fsm in the given file + * + * @param filename filename + * @throws IOException IOException + */ + public void save(File filename) throws IOException { + save(new FileOutputStream(filename)); + } + + /** + * Stores the circuit in the given file + * + * @param out the writer + * @throws IOException IOException + */ + public void save(OutputStream out) throws IOException { + try (Writer w = new OutputStreamWriter(out, "utf-8")) { + XStream xStream = getxStream(); + w.write("\n"); + xStream.marshal(this, new PrettyPrintWriter(w)); + modified = false; + if (modifiedListener != null) + modifiedListener.modifiedChanged(modified); + } + } /** * Creates a new FSM containing the given states @@ -45,6 +132,7 @@ public class FSM { public FSM add(State state) { if (state.getNumber() < 0) state.setNumber(states.size()); + state.setFSM(this); states.add(state); return this; } @@ -57,6 +145,7 @@ public class FSM { */ public FSM add(Transition transition) { transitions.add(transition); + transition.setFSM(this); return this; } @@ -96,9 +185,9 @@ public class FSM { */ public FSM transition(State from, State to, String condition) { if (!states.contains(from)) - states.add(from); + add(from); if (!states.contains(to)) - states.add(to); + add(to); return add(new Transition(from, to, condition)); } @@ -190,8 +279,10 @@ public class FSM { /** * Orders all states in a big circle + * + * @return this for chained calls */ - public void circle() { + public FSM circle() { double delta = 2 * Math.PI / states.size(); double rad = 0; for (State s : states) @@ -207,6 +298,8 @@ public class FSM { for (Transition t : transitions) t.initPos(); + + return this; } /** @@ -260,6 +353,7 @@ public class FSM { */ public void remove(Transition transition) { transitions.remove(transition); + wasModified(); } /** @@ -270,5 +364,43 @@ public class FSM { public void remove(State state) { states.remove(state); transitions.removeIf(t -> t.getStartState() == state || t.getTargetState() == state); + wasModified(); + } + + /** + * Marks the fsm as modified + */ + void wasModified() { + modified = true; + if (modifiedListener != null) + modifiedListener.modifiedChanged(modified); + } + + /** + * Sets a modified listener + * + * @param modifiedListener the listener called if fsm was modified + */ + public void setModifiedListener(ModifiedListener modifiedListener) { + this.modifiedListener = modifiedListener; + } + + /** + * @return true if fsm has changed + */ + public boolean hasChanged() { + return modified; + } + + /** + * a modified listener + */ + public interface ModifiedListener { + /** + * called if fsm was modified + * + * @param wasModified true is fsm is modified + */ + void modifiedChanged(boolean wasModified); } } diff --git a/src/main/java/de/neemann/digital/fsm/Movable.java b/src/main/java/de/neemann/digital/fsm/Movable.java index 17b5160a6..0c3e4090b 100644 --- a/src/main/java/de/neemann/digital/fsm/Movable.java +++ b/src/main/java/de/neemann/digital/fsm/Movable.java @@ -12,8 +12,9 @@ import de.neemann.digital.draw.graphics.VectorFloat; */ public class Movable { private VectorFloat position; - private VectorFloat speed; - private VectorFloat force; + private transient VectorFloat speed; + private transient VectorFloat force; + private transient FSM fsm; /** * Creates a new instance @@ -30,7 +31,15 @@ public class Movable { * @param position the position */ public void setPos(VectorFloat position) { - this.position = position; + if (!this.position.equals(position)) { + this.position = position; + wasModified(); + } + } + + void wasModified() { + if (fsm != null) + fsm.wasModified(); } /** @@ -39,7 +48,10 @@ public class Movable { * @param df the force to add */ public void addToForce(VectorFloat df) { - force = force.add(df); + if (force == null) + force = df; + else + force = force.add(df); } /** @@ -92,6 +104,8 @@ public class Movable { * @return the force */ public VectorFloat getForce() { + if (force == null) + resetForce(); return force; } @@ -115,9 +129,19 @@ public class Movable { * @param dt the time step */ public void move(int dt) { - speed = speed.add(force.mul(dt / 200f)); + if (speed == null) + speed = force.mul(dt / 200f); + else + speed = speed.add(force.mul(dt / 200f)); setPos(position.add(speed.mul(dt / 1000f))); speed = speed.mul(0.7f); } + void setFSM(FSM fsm) { + this.fsm = fsm; + } + + FSM getFsm() { + return fsm; + } } diff --git a/src/main/java/de/neemann/digital/fsm/State.java b/src/main/java/de/neemann/digital/fsm/State.java index e3e9e7107..af2a1379b 100644 --- a/src/main/java/de/neemann/digital/fsm/State.java +++ b/src/main/java/de/neemann/digital/fsm/State.java @@ -47,8 +47,11 @@ public class State extends Movable { * @return this for chained calls */ public State setValues(String values) { - this.values = values; - valueMap = null; + if (!this.values.equals(values)) { + this.values = values; + valueMap = null; + wasModified(); + } return this; } @@ -100,7 +103,10 @@ public class State extends Movable { * @param name the name to set */ public void setName(String name) { - this.name = name; + if (!this.name.equals(name)) { + this.name = name; + wasModified(); + } } /** @@ -172,7 +178,10 @@ public class State extends Movable { * @return this for chained calls */ public State setNumber(int number) { - this.number = number; + if (this.number!=number) { + this.number = number; + wasModified(); + } return this; } diff --git a/src/main/java/de/neemann/digital/fsm/Transition.java b/src/main/java/de/neemann/digital/fsm/Transition.java index 42ac2175e..11113a979 100644 --- a/src/main/java/de/neemann/digital/fsm/Transition.java +++ b/src/main/java/de/neemann/digital/fsm/Transition.java @@ -93,7 +93,7 @@ public class Transition extends Movable { if (fromState != toState) { VectorFloat dist = fromState.getPos().sub(toState.getPos()); VectorFloat p = position.sub(fromState.getPos()); - VectorFloat n = new VectorFloat(dist.getYFloat(), -dist.getXFloat()).norm(); + VectorFloat n = dist.getOrthogonal().norm(); float l = p.mul(n); super.setPos(fromState.getPos().sub(dist.mul(0.5f)).add(n.mul(l))); } else @@ -132,7 +132,7 @@ public class Transition extends Movable { gr.drawPolygon(p, arrowStyle); // arrow - VectorFloat lot = new VectorFloat(difTo.getYFloat(), -difTo.getXFloat()).mul(0.5f); + VectorFloat lot = difTo.getOrthogonal().mul(0.5f); gr.drawPolygon(new Polygon(false) .add(end.add(difTo.add(lot).mul(0.2f))) .add(arrowTip) @@ -156,8 +156,11 @@ public class Transition extends Movable { * @param condition the condition */ public void setCondition(String condition) { - this.condition = condition; - conditionExpression = null; + if (!this.condition.equals(condition)) { + this.condition = condition; + wasModified(); + conditionExpression = null; + } } /** diff --git a/src/main/java/de/neemann/digital/fsm/gui/FSMComponent.java b/src/main/java/de/neemann/digital/fsm/gui/FSMComponent.java index 2841ca55e..ad6dbde01 100644 --- a/src/main/java/de/neemann/digital/fsm/gui/FSMComponent.java +++ b/src/main/java/de/neemann/digital/fsm/gui/FSMComponent.java @@ -8,10 +8,8 @@ package de.neemann.digital.fsm.gui; import de.neemann.digital.core.element.ElementAttributes; import de.neemann.digital.core.element.Key; import de.neemann.digital.core.element.Keys; -import de.neemann.digital.draw.graphics.GraphicMinMax; -import de.neemann.digital.draw.graphics.GraphicSwing; -import de.neemann.digital.draw.graphics.Style; -import de.neemann.digital.draw.graphics.Vector; +import de.neemann.digital.draw.graphics.*; +import de.neemann.digital.draw.graphics.Polygon; import de.neemann.digital.fsm.FSM; import de.neemann.digital.fsm.Movable; import de.neemann.digital.fsm.State; @@ -50,14 +48,8 @@ public class FSMComponent extends JComponent { /** * Creates a new component - * - * @param fsm the fsm to visualize */ - public FSMComponent(FSM fsm) { - this.fsm = fsm; - - fsm.circle(); - + public FSMComponent() { addMouseWheelListener(e -> { Vector pos = getPosVector(e); double f = Math.pow(0.9, e.getWheelRotation()); @@ -69,6 +61,7 @@ public class FSMComponent extends JComponent { }); MouseAdapter mouseListener = new MouseAdapter() { + private boolean screenDrag; private Vector newTransitionStartPos; private Vector delta; private Vector pos; @@ -77,6 +70,7 @@ public class FSMComponent extends JComponent { public void mousePressed(MouseEvent e) { pos = new Vector(e.getX(), e.getY()); final Vector posVector = getPosVector(e); + screenDrag=false; if (mouse.isPrimaryClick(e)) { elementMoved = fsm.getMovable(posVector); if (elementMoved != null) @@ -88,13 +82,16 @@ public class FSMComponent extends JComponent { newTransitionFromState = (State) st; repaint(); } + screenDrag=true; } } @Override public void mouseReleased(MouseEvent mouseEvent) { - if (elementMoved instanceof State) + if (elementMoved instanceof State) { ((State) elementMoved).toRaster(); + repaint(); + } elementMoved = null; if (newTransitionFromState != null) { final Vector posVector = getPosVector(mouseEvent); @@ -130,7 +127,7 @@ public class FSMComponent extends JComponent { @Override public void mouseDragged(MouseEvent e) { lastMousePos = getPosVector(e); - if (elementMoved == null && newTransitionFromState == null) { + if (elementMoved == null && newTransitionFromState == null && screenDrag) { Vector newPos = new Vector(e.getX(), e.getY()); Vector delta = newPos.sub(pos); double s = transform.getScaleX(); @@ -139,8 +136,10 @@ public class FSMComponent extends JComponent { isManualScale = true; repaint(); } - if (elementMoved != null) + if (elementMoved != null) { elementMoved.setPos(getPosVector(e).sub(delta).toFloat()); + repaint(); + } if (newTransitionFromState != null) repaint(); } @@ -177,6 +176,7 @@ public class FSMComponent extends JComponent { private void createNewState(Vector posVector, Point point) { ElementAttributes attr = new ElementAttributes(); + attr.set(KEY_NUMBER, fsm.getStates().size()); SwingUtilities.convertPointToScreen(point, this); AttributeDialog ad = new AttributeDialog(SwingUtilities.getWindowAncestor(this), point, attr, Keys.LABEL, KEY_NUMBER, KEY_VALUES); ad.setTitle(Lang.get("msg_fsmNewState")); @@ -239,34 +239,50 @@ public class FSMComponent extends JComponent { * Fits the FSM to the window */ public void fitFSM() { - GraphicMinMax gr = new GraphicMinMax(); - fsm.drawTo(gr); + if (fsm != null) { + GraphicMinMax gr = new GraphicMinMax(); + fsm.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); + 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 + 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 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; - repaint(); + 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; + repaint(); + } } } + /** + * scales the fsm + * + * @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; + repaint(); + } + /** * @return the element picked by the mouse */ @@ -290,7 +306,25 @@ public class FSMComponent extends JComponent { GraphicSwing gr = new GraphicSwing(gr2, 1); fsm.drawTo(gr); - if (newTransitionFromState != null) - gr.drawLine(newTransitionFromState.getPos(), lastMousePos, Style.NORMAL); + if (newTransitionFromState != null) { + VectorFloat d = lastMousePos.sub(newTransitionFromState.getPos()).norm().mul(16f); + VectorFloat a = d.getOrthogonal().norm().mul(8f); + gr.drawPolygon(new Polygon(false) + .add(lastMousePos.sub(d).add(a)) + .add(lastMousePos) + .add(lastMousePos.sub(d).sub(a)), Style.NORMAL); + gr.drawLine(newTransitionFromState.getPos(), lastMousePos.sub(d.mul(0.2f)), Style.NORMAL); + } + } + + /** + * Sets the fsm to show + * + * @param fsm the fsm to show + */ + public void setFSM(FSM fsm) { + this.fsm = fsm; + fitFSM(); + repaint(); } } diff --git a/src/main/java/de/neemann/digital/fsm/gui/FSMFrame.java b/src/main/java/de/neemann/digital/fsm/gui/FSMFrame.java index df08f1e71..6dbb12be5 100644 --- a/src/main/java/de/neemann/digital/fsm/gui/FSMFrame.java +++ b/src/main/java/de/neemann/digital/fsm/gui/FSMFrame.java @@ -9,26 +9,41 @@ import de.neemann.digital.draw.library.ElementLibrary; import de.neemann.digital.draw.shapes.ShapeFactory; import de.neemann.digital.fsm.FSM; import de.neemann.digital.fsm.FSMDemos; +import de.neemann.digital.gui.SaveAsHelper; import de.neemann.digital.gui.components.table.TableDialog; import de.neemann.digital.lang.Lang; -import de.neemann.gui.ErrorMessage; -import de.neemann.gui.ToolTipAction; +import de.neemann.gui.*; import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; import java.awt.*; import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; +import java.io.File; +import java.io.IOException; /** * The dialog to show the FSM */ -public class FSMFrame extends JFrame { +public class FSMFrame extends JFrame implements ClosingWindowListener.ConfirmSave, FSM.ModifiedListener { + private static final Icon ICON_NEW = IconCreator.create("document-new.png"); + private static final Icon ICON_OPEN = IconCreator.create("document-open.png"); + private static final Icon ICON_SAVE = IconCreator.create("document-save.png"); + private static final Icon ICON_SAVE_AS = IconCreator.create("document-save-as.png"); + private static final Icon ICON_EXPAND = IconCreator.create("View-zoom-fit.png"); + 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 final FSM fsm; + private FSM fsm; private final FSMComponent fsmComponent; private final Timer timer; + private final JComboBox moveControl; private boolean moveStates = false; + private ToolTipAction save; + private File filename; + private boolean lastModified; /** * Use only for tests! @@ -45,7 +60,6 @@ public class FSMFrame extends JFrame { return library; } - /** * Creates a new instance * @@ -56,12 +70,12 @@ public class FSMFrame extends JFrame { public FSMFrame(JFrame parent, FSM givenFsm, ElementLibrary library) { super(Lang.get("fsm_title")); setDefaultCloseOperation(DISPOSE_ON_CLOSE); - if (givenFsm == null) + if (givenFsm == null) { givenFsm = FSMDemos.rotDecoder(); + givenFsm.circle(); + } - this.fsm = givenFsm; - - fsmComponent = new FSMComponent(fsm); + fsmComponent = new FSMComponent(); getContentPane().add(fsmComponent, BorderLayout.CENTER); timer = new Timer(100, new AbstractAction() { @@ -73,8 +87,6 @@ public class FSMFrame extends JFrame { } }); - timer.start(); - addWindowListener(new WindowAdapter() { @Override public void windowClosed(WindowEvent windowEvent) { @@ -83,6 +95,12 @@ public class FSMFrame extends JFrame { }); JMenuBar bar = new JMenuBar(); + JToolBar toolBar = new JToolBar(); + + createFileMenu(bar, toolBar); + toolBar.addSeparator(); + createViewMenu(bar, toolBar); + toolBar.addSeparator(); JMenu create = new JMenu(Lang.get("menu_fsm_create")); bar.add(create); @@ -97,44 +115,222 @@ public class FSMFrame extends JFrame { } }.createJMenuItem()); - JToolBar toolBar = new JToolBar(); - final JCheckBox moveCheck = new JCheckBox(Lang.get("fsm_move")); - moveCheck.addActionListener(new AbstractAction() { + moveControl = new JComboBox<>(new String[]{ + Lang.get("fsm_noMove"), Lang.get("fsm_moveTrans"), Lang.get("fsm_moveStates")}); + moveControl.setSelectedIndex(0); + moveControl.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent actionEvent) { - moveStates = moveCheck.isSelected(); - if (!moveStates) { - fsm.toRaster(); - repaint(); + switch (moveControl.getSelectedIndex()) { + case 0: + timer.stop(); + fsm.toRaster(); + fsmComponent.repaint(); + break; + case 1: + moveStates = false; + timer.start(); + break; + case 2: + moveStates = true; + timer.start(); + break; } } }); - moveCheck.setSelected(moveStates); - toolBar.add(moveCheck); + JPanel movePanel = new JPanel(new BorderLayout()); + movePanel.add(moveControl, BorderLayout.WEST); + toolBar.add(movePanel); getContentPane().add(toolBar, BorderLayout.PAGE_START); - setJMenuBar(bar); pack(); - fsmComponent.fitFSM(); + setFSM(givenFsm); + setLocationRelativeTo(parent); } + private void createFileMenu(JMenuBar bar, JToolBar toolBar) { + ToolTipAction newFile = new ToolTipAction(Lang.get("menu_new"), ICON_NEW) { + @Override + public void actionPerformed(ActionEvent e) { + if (ClosingWindowListener.checkForSave(FSMFrame.this, FSMFrame.this)) { + setFSM(new FSM()); + setFilename(null); + } + } + }.setAcceleratorCTRLplus('N').setToolTip(Lang.get("menu_new_tt")); + + ToolTipAction open = new ToolTipAction(Lang.get("menu_open"), ICON_OPEN) { + @Override + public void actionPerformed(ActionEvent e) { + if (ClosingWindowListener.checkForSave(FSMFrame.this, FSMFrame.this)) { + JFileChooser fc = getJFileChooser(filename); + if (fc.showOpenDialog(FSMFrame.this) == JFileChooser.APPROVE_OPTION) { + loadFile(fc.getSelectedFile()); + } + } + } + }.setAcceleratorCTRLplus('O'); + +// JMenu openRecent = new JMenu(Lang.get("menu_openRecent")); +// JMenu openRecentNewWindow = new JMenu(Lang.get("menu_openRecentNewWindow")); +// fileHistory.setMenu(openRecent, openRecentNewWindow); + + ToolTipAction saveAs = new ToolTipAction(Lang.get("menu_saveAs"), ICON_SAVE_AS) { + @Override + public void actionPerformed(ActionEvent e) { + JFileChooser fc = getJFileChooser(filename); + final SaveAsHelper saveAsHelper = new SaveAsHelper(FSMFrame.this, fc, "fsm"); + saveAsHelper.checkOverwrite(file -> saveFile(file)); + } + }; + + save = new ToolTipAction(Lang.get("menu_save"), ICON_SAVE) { + @Override + public void actionPerformed(ActionEvent e) { + if (filename == null) + saveAs.actionPerformed(e); + else + saveFile(filename); + } + }.setAcceleratorCTRLplus('S').setEnabledChain(false); + + JMenu file = new JMenu(Lang.get("menu_file")); + bar.add(file); + file.add(newFile.createJMenuItem()); + file.add(open.createJMenuItem()); + file.add(save.createJMenuItem()); + file.add(saveAs.createJMenuItem()); + + toolBar.add(newFile.createJButtonNoText()); + toolBar.add(open.createJButtonNoText()); + toolBar.add(save.createJButtonNoText()); + } + + private void setFSM(FSM fsm) { + this.fsm = fsm; + fsmComponent.setFSM(fsm); + fsm.setModifiedListener(this); + } + + private static JFileChooser getJFileChooser(File filename) { + File folder = null; + if (filename != null) + folder = filename.getParentFile(); + + JFileChooser fileChooser = new MyFileChooser(folder); + fileChooser.setFileFilter(new FileNameExtensionFilter("FSM", "fsm")); + return fileChooser; + } + + private void setFilename(File filename) { + String fsmTitle; + if (filename == null) + fsmTitle = Lang.get("fsm_title"); + else + fsmTitle = filename.toString() + " - " + Lang.get("fsm_title"); + + if (fsm.hasChanged()) + fsmTitle = "*" + fsmTitle; + setTitle(fsmTitle); + + this.filename = filename; + } + + @Override + public boolean isStateChanged() { + return fsm.hasChanged(); + } + + @Override + public void saveChanges() { + save.actionPerformed(null); + } + + @Override + public void modifiedChanged(boolean modified) { + if (lastModified != modified) { + lastModified = modified; + setFilename(filename); + save.setEnabled(modified); + } + } + + private void loadFile(File file) { + try { + moveControl.setSelectedIndex(0); + setFSM(FSM.loadFSM(file)); + setFilename(file); + } catch (IOException e) { + new ErrorMessage(Lang.get("msg_fsm_errorLoadingFile")).addCause(e).show(this); + } + } + + private void saveFile(File file) { + try { + fsm.save(file); + setFilename(file); + save.setEnabled(false); + } catch (IOException e) { + new ErrorMessage(Lang.get("msg_fsm_errorStoringFile")).addCause(e).show(this); + } + } + + private void createViewMenu(JMenuBar menuBar, JToolBar toolBar) { + ToolTipAction maximize = new ToolTipAction(Lang.get("menu_maximize"), ICON_EXPAND) { + @Override + public void actionPerformed(ActionEvent e) { + fsmComponent.fitFSM(); + } + }.setAccelerator("F1"); + ToolTipAction zoomIn = new ToolTipAction(Lang.get("menu_zoomIn"), ICON_ZOOM_IN) { + @Override + public void actionPerformed(ActionEvent e) { + fsmComponent.scaleCircuit(1 / 0.9); + } + }.setAccelerator("control PLUS"); + // enable [+] which is SHIFT+[=] on english keyboard layout + fsmComponent.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, KeyEvent.CTRL_DOWN_MASK, false), zoomIn); + fsmComponent.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, KeyEvent.CTRL_DOWN_MASK, false), zoomIn); + fsmComponent.getActionMap().put(zoomIn, zoomIn); + + ToolTipAction zoomOut = new ToolTipAction(Lang.get("menu_zoomOut"), ICON_ZOOM_OUT) { + @Override + public void actionPerformed(ActionEvent e) { + fsmComponent.scaleCircuit(0.9); + } + }.setAccelerator("control MINUS"); + // enable [+] which is SHIFT+[=] on english keyboard layout + fsmComponent.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, KeyEvent.CTRL_DOWN_MASK, false), zoomOut); + fsmComponent.getActionMap().put(zoomOut, zoomOut); + + toolBar.add(zoomIn.createJButtonNoText()); + toolBar.add(zoomOut.createJButtonNoText()); + toolBar.add(maximize.createJButtonNoText()); + + JMenu view = new JMenu(Lang.get("menu_view")); + menuBar.add(view); + view.add(maximize.createJMenuItem()); + view.add(zoomOut.createJMenuItem()); + view.add(zoomIn.createJMenuItem()); + } + + /** * A simple test method * * @param args the programs arguments */ public static void main(String[] args) { - FSM fsm = FSMDemos.rotDecoder(); ElementLibrary library = new ElementLibrary(); new ShapeFactory(library); - new FSMFrame(null, fsm, library).setVisible(true); - + new FSMFrame(null, fsm.circle(), library).setVisible(true); } + } diff --git a/src/main/resources/lang/lang_de.xml b/src/main/resources/lang/lang_de.xml index 58c374b88..d7c848416 100644 --- a/src/main/resources/lang/lang_de.xml +++ b/src/main/resources/lang/lang_de.xml @@ -1760,13 +1760,14 @@ Daher steht auch das Signal 'D_out' zur Verfügung, um in diesem Fall den Wert z Endlicher Automat Erzeugen Zustandsübergangstabelle - Bewegen Der Automat ist nicht deterministisch: {0} Zustandsnummer {0} ist nicht eindeutig. Es gibt keinen Initialzustand. Zustand ''{0}'' nicht gefunden! Falsche Zuweisung an einen Ausgang (''{0}'')! Fehler in Bedingung ''{0}''! + Fehler beim Laden der Datei! + Fehler beim Speichern der Datei! Zustandsnummer Die Nummer welche diesen Zustand representiert. Ausgänge @@ -1777,5 +1778,8 @@ Daher steht auch das Signal 'D_out' zur Verfügung, um in diesem Fall den Wert z Bedingung Ein boolscher Ausdruck. Neuer Zustand + keine Bewegung + Übergänge + Übergänge+Zustände diff --git a/src/main/resources/lang/lang_en.xml b/src/main/resources/lang/lang_en.xml index 581a648a1..853f39b80 100644 --- a/src/main/resources/lang/lang_en.xml +++ b/src/main/resources/lang/lang_en.xml @@ -1740,13 +1740,14 @@ Therefore, the signal 'D_out' is also available to check the value in this case. Finite State Machine Create State Transition Table - Move The FSM is not deterministic: {0} State Number {0} used twice. There is no initial state (state number zero). State ''{0}'' not found! Wrong assignment to output (''{0}'')! Error in condition ''{0}''! + Error loading a file! + Error storing a file! State Number The number which represents this state. Outputs @@ -1757,5 +1758,8 @@ Therefore, the signal 'D_out' is also available to check the value in this case. Condition A boolean expression. New State + no movement + Transitions + Transitions+States \ No newline at end of file