diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/SaveAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/SaveAction.java new file mode 100644 index 0000000..d9cc06b --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/control/actions/SaveAction.java @@ -0,0 +1,63 @@ +package nl.andrewlalis.erme.control.actions; + +import lombok.Setter; +import nl.andrewlalis.erme.model.MappingModel; + +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.io.*; + +public class SaveAction extends AbstractAction { + private static SaveAction instance; + + public static SaveAction getInstance() { + if (instance == null) { + instance = new SaveAction(); + } + return instance; + } + + private File lastSelectedFile; + + @Setter + private MappingModel model; + + public SaveAction() { + super("Save"); + this.putValue(SHORT_DESCRIPTION, "Save the current diagram to a file."); + this.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); + } + + @Override + public void actionPerformed(ActionEvent e) { + JFileChooser fileChooser = new JFileChooser(this.lastSelectedFile); + FileNameExtensionFilter filter = new FileNameExtensionFilter( + "ERME Serialized Files", + "erme" + ); + fileChooser.setFileFilter(filter); + int choice = fileChooser.showSaveDialog((Component) e.getSource()); + if (choice == JFileChooser.APPROVE_OPTION) { + File chosenFile = fileChooser.getSelectedFile(); + if (chosenFile == null || chosenFile.isDirectory()) { + JOptionPane.showMessageDialog(fileChooser, "The selected file cannot be written to.", "Invalid File", JOptionPane.WARNING_MESSAGE); + return; + } + if (!chosenFile.exists() && !chosenFile.getName().endsWith(".erme")) { + chosenFile = new File(chosenFile.getParent(), chosenFile.getName() + ".erme"); + } + // TODO: Check for confirm before overwriting. + try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(chosenFile))) { + oos.writeObject(this.model); + this.lastSelectedFile = chosenFile; + JOptionPane.showMessageDialog(fileChooser, "File saved successfully.", "Success", JOptionPane.INFORMATION_MESSAGE); + } catch (IOException ex) { + JOptionPane.showMessageDialog(fileChooser, "An error occurred and the file could not be saved:\n" + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); + } + } + } +} diff --git a/src/main/java/nl/andrewlalis/erme/control/diagram/DiagramMouseListener.java b/src/main/java/nl/andrewlalis/erme/control/diagram/DiagramMouseListener.java index ec1cf7d..45ab1f4 100644 --- a/src/main/java/nl/andrewlalis/erme/control/diagram/DiagramMouseListener.java +++ b/src/main/java/nl/andrewlalis/erme/control/diagram/DiagramMouseListener.java @@ -18,23 +18,32 @@ public class DiagramMouseListener extends MouseAdapter { this.model = model; } + /** + * Handles mouse presses. This is what should happen: + * - If the click occurs outside of the bounds of any relation, deselect all + * relations, if CTRL is not held down. + * - If the click occurs within at least one relation, select the first one, + * and deselect all others if CTRL is not held down. + * @param e The mouse event. + */ @Override public void mousePressed(MouseEvent e) { DiagramPanel panel = (DiagramPanel) e.getSource(); final Graphics2D g2d = panel.getGraphics2D(); this.mouseDragStart = e.getPoint(); - if ((e.getModifiers() & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK) { - boolean hit = false; - for (Relation r : this.model.getRelations()) { - if (r.getViewModel().getBounds(g2d).contains(e.getX(), e.getY())) { - r.setSelected(!r.isSelected()); - hit = true; - } - } - if (!hit) { - this.model.getRelations().forEach(r -> r.setSelected(false)); + + boolean isCtrlDown = (e.getModifiers() & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK; + + if (!isCtrlDown) { + this.model.getRelations().forEach(r -> r.setSelected(false)); + } + for (Relation r : this.model.getRelations()) { + if (r.getViewModel().getBounds(g2d).contains(e.getX(), e.getY())) { + r.setSelected(!r.isSelected()); + break; } } + this.model.fireChangedEvent(); } @Override @@ -46,11 +55,16 @@ public class DiagramMouseListener extends MouseAdapter { public void mouseDragged(MouseEvent e) { int dx = this.mouseDragStart.x - e.getX(); int dy = this.mouseDragStart.y - e.getY(); + boolean changed = false; for (Relation r : this.model.getRelations()) { if (r.isSelected()) { r.setPosition(new Point(r.getPosition().x - dx, r.getPosition().y - dy)); + changed = true; } } + if (changed) { + this.model.fireChangedEvent(); + } this.mouseDragStart = e.getPoint(); } diff --git a/src/main/java/nl/andrewlalis/erme/model/Attribute.java b/src/main/java/nl/andrewlalis/erme/model/Attribute.java index 793b8cb..9f4e84e 100644 --- a/src/main/java/nl/andrewlalis/erme/model/Attribute.java +++ b/src/main/java/nl/andrewlalis/erme/model/Attribute.java @@ -2,13 +2,14 @@ package nl.andrewlalis.erme.model; import lombok.Getter; +import java.io.Serializable; import java.util.Objects; /** * A single value that belongs to a relation. */ @Getter -public class Attribute { +public class Attribute implements Serializable { private final Relation relation; private AttributeType type; private String name; diff --git a/src/main/java/nl/andrewlalis/erme/model/MappingModel.java b/src/main/java/nl/andrewlalis/erme/model/MappingModel.java index 2747e60..331128e 100644 --- a/src/main/java/nl/andrewlalis/erme/model/MappingModel.java +++ b/src/main/java/nl/andrewlalis/erme/model/MappingModel.java @@ -2,6 +2,7 @@ package nl.andrewlalis.erme.model; import lombok.Getter; +import java.io.Serializable; import java.util.HashSet; import java.util.Objects; import java.util.Set; @@ -10,11 +11,11 @@ import java.util.Set; * This model contains all the information about a single mapping diagram, * including each mapped table and the links between them. */ -public class MappingModel { +public class MappingModel implements Serializable { @Getter private final Set relations; - private final Set changeListeners; + private transient final Set changeListeners; public MappingModel() { this.relations = new HashSet<>(); @@ -38,7 +39,7 @@ public class MappingModel { listener.onModelChanged(); } - protected final void fireChangedEvent() { + public final void fireChangedEvent() { this.changeListeners.forEach(ModelChangeListener::onModelChanged); } diff --git a/src/main/java/nl/andrewlalis/erme/model/Relation.java b/src/main/java/nl/andrewlalis/erme/model/Relation.java index bae96cd..37b95bd 100644 --- a/src/main/java/nl/andrewlalis/erme/model/Relation.java +++ b/src/main/java/nl/andrewlalis/erme/model/Relation.java @@ -4,6 +4,7 @@ import lombok.Getter; import nl.andrewlalis.erme.view.view_models.RelationViewModel; import java.awt.*; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -12,7 +13,7 @@ import java.util.Objects; * Represents a single "relation" or table in the diagram. */ @Getter -public class Relation { +public class Relation implements Serializable { private final MappingModel model; private Point position; private String name; @@ -30,24 +31,15 @@ public class Relation { } public void setPosition(Point position) { - if (!this.position.equals(position)) { - this.position = position; - this.model.fireChangedEvent(); - } + this.position = position; } public void setName(String name) { - if (!this.name.equals(name)) { - this.name = name; - this.model.fireChangedEvent(); - } + this.name = name; } public void setSelected(boolean selected) { - if (selected != this.selected) { - this.selected = selected; - this.model.fireChangedEvent(); - } + this.selected = selected; } public void addAttribute(Attribute attribute) { diff --git a/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java b/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java index 544e70a..2a8a617 100644 --- a/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java +++ b/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java @@ -1,6 +1,7 @@ package nl.andrewlalis.erme.view; import lombok.Getter; +import nl.andrewlalis.erme.control.actions.SaveAction; import nl.andrewlalis.erme.control.diagram.DiagramMouseListener; import nl.andrewlalis.erme.model.MappingModel; import nl.andrewlalis.erme.model.ModelChangeListener; @@ -35,6 +36,7 @@ public class DiagramPanel extends JPanel implements ModelChangeListener { DiagramMouseListener listener = new DiagramMouseListener(newModel); this.addMouseListener(listener); this.addMouseMotionListener(listener); + SaveAction.getInstance().setModel(newModel); this.repaint(); } diff --git a/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java b/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java index 55cdf9a..ed02ffa 100644 --- a/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java +++ b/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java @@ -1,9 +1,6 @@ package nl.andrewlalis.erme.view; -import nl.andrewlalis.erme.control.actions.ExitAction; -import nl.andrewlalis.erme.control.actions.ExportToImageAction; -import nl.andrewlalis.erme.control.actions.RedoAction; -import nl.andrewlalis.erme.control.actions.UndoAction; +import nl.andrewlalis.erme.control.actions.*; import javax.swing.*; @@ -18,6 +15,8 @@ public class EditorMenuBar extends JMenuBar { private JMenu buildFileMenu() { JMenu menu = new JMenu("File"); + JMenuItem saveItem = new JMenuItem(SaveAction.getInstance()); + menu.add(saveItem); JMenuItem exportAsImageItem = new JMenuItem(ExportToImageAction.getInstance()); menu.add(exportAsImageItem); JMenuItem exitItem = new JMenuItem(ExitAction.getInstance()); diff --git a/src/main/java/nl/andrewlalis/erme/view/view_models/RelationViewModel.java b/src/main/java/nl/andrewlalis/erme/view/view_models/RelationViewModel.java index 6ddc081..5d1a247 100644 --- a/src/main/java/nl/andrewlalis/erme/view/view_models/RelationViewModel.java +++ b/src/main/java/nl/andrewlalis/erme/view/view_models/RelationViewModel.java @@ -29,10 +29,8 @@ public class RelationViewModel implements ViewModel { } if (this.relation.isSelected()) { g.setColor(Color.BLUE); - } else { - g.setColor(Color.CYAN); + g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height); } - g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height); } public Rectangle getBounds(Graphics2D g) {