diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/ExportToImageAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/ExportToImageAction.java index 9fe74ac..187ac39 100644 --- a/src/main/java/nl/andrewlalis/erme/control/actions/ExportToImageAction.java +++ b/src/main/java/nl/andrewlalis/erme/control/actions/ExportToImageAction.java @@ -124,10 +124,10 @@ public class ExportToImageAction extends DiagramPanelAction { // Render the model. boolean lolcat = model.isLolcatEnabled(); // save previous lolcat mode model.setLolcatEnabled(false); - List selectedRelations = model.getSelectedRelations(); - model.getSelectedRelations().forEach(r -> r.setSelected(false)); + List selectedRelations = model.getSelectionModel().getSelectedRelations(); + model.getSelectionModel().clearSelection(); new MappingModelViewModel(model).draw(g2d); - model.getRelations().forEach(r -> r.setSelected(selectedRelations.contains(r))); + model.getSelectionModel().selectAll(selectedRelations); model.setLolcatEnabled(lolcat); // revert previous lolcat mode // Revert to the normal image space, and render a watermark. diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/LoadSampleModelAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/LoadSampleModelAction.java index 6e5d6e3..e93bd9a 100644 --- a/src/main/java/nl/andrewlalis/erme/control/actions/LoadSampleModelAction.java +++ b/src/main/java/nl/andrewlalis/erme/control/actions/LoadSampleModelAction.java @@ -1,6 +1,9 @@ package nl.andrewlalis.erme.control.actions; -import nl.andrewlalis.erme.model.*; +import nl.andrewlalis.erme.model.Attribute; +import nl.andrewlalis.erme.model.AttributeType; +import nl.andrewlalis.erme.model.MappingModel; +import nl.andrewlalis.erme.model.Relation; import nl.andrewlalis.erme.view.DiagramPanel; import java.awt.*; @@ -22,7 +25,9 @@ public class LoadSampleModelAction extends DiagramPanelAction { Relation r1 = new Relation(model, new Point(50, 100), "Airplane"); r1.addAttribute(new Attribute(r1, AttributeType.ID_KEY, "id")); r1.addAttribute(new Attribute(r1, AttributeType.PLAIN, "purchasedAt")); - r1.addAttribute(new ForeignKeyAttribute(r1, AttributeType.PLAIN, "typeName", "AirplaneType", "name")); + Attribute fk = new Attribute(r1, AttributeType.PLAIN, "typeName"); + fk.setReference(model.findAttribute("AirplaneType", "name")); + r1.addAttribute(fk); model.addRelation(r1); getDiagramPanel().setModel(model); } diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/edits/AddAttributeAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/edits/AddAttributeAction.java index 949faa8..87ae8de 100644 --- a/src/main/java/nl/andrewlalis/erme/control/actions/edits/AddAttributeAction.java +++ b/src/main/java/nl/andrewlalis/erme/control/actions/edits/AddAttributeAction.java @@ -3,6 +3,7 @@ package nl.andrewlalis.erme.control.actions.edits; import nl.andrewlalis.erme.control.actions.DiagramPanelAction; import nl.andrewlalis.erme.model.*; import nl.andrewlalis.erme.view.DiagramPanel; +import nl.andrewlalis.erme.view.EditAttributePopupDialog; import javax.swing.*; import java.awt.*; @@ -23,7 +24,7 @@ public class AddAttributeAction extends DiagramPanelAction { public void actionPerformed(ActionEvent e) { DiagramPanel dp = getDiagramPanel(); MappingModel model = dp.getModel(); - List selectedRelations = model.getSelectedRelations(); + List selectedRelations = model.getSelectionModel().getSelectedRelations(); if (selectedRelations.size() != 1) { JOptionPane.showMessageDialog( dp, @@ -34,72 +35,74 @@ public class AddAttributeAction extends DiagramPanelAction { return; } Relation r = selectedRelations.get(0); - Attribute createdAttribute; - String name = JOptionPane.showInputDialog(dp, "Enter the name of the attribute.", "Attribute Name", JOptionPane.PLAIN_MESSAGE); - if (name == null) return; - Integer index = (Integer) JOptionPane.showInputDialog( - dp, - "Select the index to insert this attribute at.", - "Attribute Index", - JOptionPane.PLAIN_MESSAGE, - null, - Stream.iterate(0, n -> n + 1).limit(r.getAttributes().size() + 1).toArray(), - r.getAttributes().size() - ); - if (index == null) return; - AttributeType type = (AttributeType) JOptionPane.showInputDialog( - dp, - "Select the type this attribute is.", - "Attribute Type", - JOptionPane.PLAIN_MESSAGE, - null, - AttributeType.values(), - AttributeType.PLAIN - ); - if (type == null) return; - boolean shouldUseForeignKey = ((String) JOptionPane.showInputDialog( - dp, - "Is this attribute a foreign key?", - "Foreign Key", - JOptionPane.PLAIN_MESSAGE, - null, - new String[]{"Yes", "No"}, - "No" - )).equalsIgnoreCase("yes"); - if (shouldUseForeignKey) { - if (model.getRelations().size() < 2) { - JOptionPane.showMessageDialog(dp, "There should be at least 2 relations present in the model.", "Not Enough Relations", JOptionPane.WARNING_MESSAGE); - return; - } - Relation fkRelation = (Relation) JOptionPane.showInputDialog( - dp, - "Select the relation that this foreign key references.", - "Foreign Key Relation Reference", - JOptionPane.PLAIN_MESSAGE, - null, - model.getRelations().toArray(new Relation[0]), - model.getRelations().stream().findFirst().orElse(null) - ); - if (fkRelation == null) return; - List eligibleAttributes = fkRelation.getAttributes(); - if (eligibleAttributes.isEmpty()) { - JOptionPane.showMessageDialog(dp, "There are no referencable attributes in the selected relation.", "No Referencable Attributes", JOptionPane.WARNING_MESSAGE); - return; - } - Attribute fkAttribute = (Attribute) JOptionPane.showInputDialog( - dp, - "Select the attribute that this foreign key references.", - "Foreign Key Attribute Reference", - JOptionPane.PLAIN_MESSAGE, - null, - eligibleAttributes.toArray(new Attribute[0]), - eligibleAttributes.get(0) - ); - if (fkAttribute == null) return; - createdAttribute = new ForeignKeyAttribute(r, type, name, fkAttribute); - } else { - createdAttribute = new Attribute(r, type, name); - } - r.addAttribute(createdAttribute, index); + EditAttributePopupDialog popup = new EditAttributePopupDialog((JFrame) SwingUtilities.getWindowAncestor(dp), r, null); + popup.setVisible(true); +// Attribute createdAttribute; +// String name = JOptionPane.showInputDialog(dp, "Enter the name of the attribute.", "Attribute Name", JOptionPane.PLAIN_MESSAGE); +// if (name == null) return; +// Integer index = (Integer) JOptionPane.showInputDialog( +// dp, +// "Select the index to insert this attribute at.", +// "Attribute Index", +// JOptionPane.PLAIN_MESSAGE, +// null, +// Stream.iterate(0, n -> n + 1).limit(r.getAttributes().size() + 1).toArray(), +// r.getAttributes().size() +// ); +// if (index == null) return; +// AttributeType type = (AttributeType) JOptionPane.showInputDialog( +// dp, +// "Select the type this attribute is.", +// "Attribute Type", +// JOptionPane.PLAIN_MESSAGE, +// null, +// AttributeType.values(), +// AttributeType.PLAIN +// ); +// if (type == null) return; +// boolean shouldUseForeignKey = ((String) JOptionPane.showInputDialog( +// dp, +// "Is this attribute a foreign key?", +// "Foreign Key", +// JOptionPane.PLAIN_MESSAGE, +// null, +// new String[]{"Yes", "No"}, +// "No" +// )).equalsIgnoreCase("yes"); +// if (shouldUseForeignKey) { +// if (model.getRelations().size() < 2) { +// JOptionPane.showMessageDialog(dp, "There should be at least 2 relations present in the model.", "Not Enough Relations", JOptionPane.WARNING_MESSAGE); +// return; +// } +// Relation fkRelation = (Relation) JOptionPane.showInputDialog( +// dp, +// "Select the relation that this foreign key references.", +// "Foreign Key Relation Reference", +// JOptionPane.PLAIN_MESSAGE, +// null, +// model.getRelations().toArray(new Relation[0]), +// model.getRelations().stream().findFirst().orElse(null) +// ); +// if (fkRelation == null) return; +// List eligibleAttributes = fkRelation.getAttributes(); +// if (eligibleAttributes.isEmpty()) { +// JOptionPane.showMessageDialog(dp, "There are no referencable attributes in the selected relation.", "No Referencable Attributes", JOptionPane.WARNING_MESSAGE); +// return; +// } +// Attribute fkAttribute = (Attribute) JOptionPane.showInputDialog( +// dp, +// "Select the attribute that this foreign key references.", +// "Foreign Key Attribute Reference", +// JOptionPane.PLAIN_MESSAGE, +// null, +// eligibleAttributes.toArray(new Attribute[0]), +// eligibleAttributes.get(0) +// ); +// if (fkAttribute == null) return; +// createdAttribute = new ForeignKeyAttribute(r, type, name, fkAttribute); +// } else { +// createdAttribute = new Attribute(r, type, name); +// } +// r.addAttribute(createdAttribute, index); } } diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/edits/AddRelationAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/edits/AddRelationAction.java index d711eb4..73ae8b1 100644 --- a/src/main/java/nl/andrewlalis/erme/control/actions/edits/AddRelationAction.java +++ b/src/main/java/nl/andrewlalis/erme/control/actions/edits/AddRelationAction.java @@ -43,9 +43,9 @@ public class AddRelationAction extends DiagramPanelAction { p = new Point(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2); } Relation r = new Relation(model, p, name); - model.getSelectedRelations().forEach(rl -> rl.setSelected(false)); - r.setSelected(true); model.addRelation(r); + model.getSelectionModel().clearSelection(); + model.getSelectionModel().select(r); if (isFirstRelation) { model.normalizeRelationPositions(); dp.centerModel(); diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/edits/RemoveAttributeAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/edits/RemoveAttributeAction.java index 4eb16d7..651aa02 100644 --- a/src/main/java/nl/andrewlalis/erme/control/actions/edits/RemoveAttributeAction.java +++ b/src/main/java/nl/andrewlalis/erme/control/actions/edits/RemoveAttributeAction.java @@ -2,7 +2,6 @@ package nl.andrewlalis.erme.control.actions.edits; import nl.andrewlalis.erme.control.actions.DiagramPanelAction; import nl.andrewlalis.erme.model.Attribute; -import nl.andrewlalis.erme.model.Relation; import nl.andrewlalis.erme.view.DiagramPanel; import javax.swing.*; @@ -20,28 +19,22 @@ public class RemoveAttributeAction extends DiagramPanelAction { @Override public void actionPerformed(ActionEvent e) { - List selectedRelations = getDiagramPanel().getModel().getSelectedRelations(); - if (selectedRelations.size() != 1 || selectedRelations.get(0).getAttributes().isEmpty()) { + List selectedAttributes = getDiagramPanel().getModel().getSelectionModel().getSelectedAttributes(); + if (selectedAttributes.isEmpty()) { JOptionPane.showMessageDialog( getDiagramPanel(), - "A single relation with at least one attribute must be selected to remove an attribute.", - "Single Relation With Attribute Required", + "At least one attribute must be selected to remove.", + "Select Attributes to Remove Them", JOptionPane.WARNING_MESSAGE ); return; } - Relation r = selectedRelations.get(0); - Attribute attribute = (Attribute) JOptionPane.showInputDialog( - getDiagramPanel(), - "Select the attribute to remove.", - "Select Attribute", - JOptionPane.PLAIN_MESSAGE, - null, - r.getAttributes().toArray(new Attribute[0]), - r.getAttributes().get(0) - ); - if (attribute != null) { - r.removeAttribute(attribute); + int choice = JOptionPane.showConfirmDialog(getDiagramPanel(), "Are you sure you want to remove these attributes?", "Confirm", JOptionPane.OK_CANCEL_OPTION); + if (choice == JOptionPane.YES_OPTION) { + for (Attribute a : selectedAttributes) { + a.getRelation().removeAttribute(a); + } + getDiagramPanel().getModel().getSelectionModel().clearAttributes(); } } } diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/edits/RemoveRelationAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/edits/RemoveRelationAction.java index cf9b6c1..8ae23ab 100644 --- a/src/main/java/nl/andrewlalis/erme/control/actions/edits/RemoveRelationAction.java +++ b/src/main/java/nl/andrewlalis/erme/control/actions/edits/RemoveRelationAction.java @@ -9,6 +9,7 @@ import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; +import java.util.List; public class RemoveRelationAction extends DiagramPanelAction { public RemoveRelationAction(DiagramPanel diagramPanel) { @@ -20,7 +21,8 @@ public class RemoveRelationAction extends DiagramPanelAction { @Override public void actionPerformed(ActionEvent e) { MappingModel model = getDiagramPanel().getModel(); - if (model.getSelectedRelations().isEmpty()) { + List selectedRelations = model.getSelectionModel().getSelectedRelations(); + if (selectedRelations.isEmpty()) { JOptionPane.showMessageDialog( getDiagramPanel(), "No relations selected. Select at least one relation to remove.", @@ -29,8 +31,12 @@ public class RemoveRelationAction extends DiagramPanelAction { ); return; } - for (Relation r : model.getSelectedRelations()) { - model.removeRelation(r); + int choice = JOptionPane.showConfirmDialog(getDiagramPanel(), "Are you sure you want to remove these relations?", "Confirm", JOptionPane.YES_NO_OPTION); + if (choice == JOptionPane.YES_OPTION) { + for (Relation r : selectedRelations) { + model.removeRelation(r); + } + model.getSelectionModel().clearRelations(); } } } 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 f9b013c..c5e60e8 100644 --- a/src/main/java/nl/andrewlalis/erme/control/diagram/DiagramMouseListener.java +++ b/src/main/java/nl/andrewlalis/erme/control/diagram/DiagramMouseListener.java @@ -1,5 +1,6 @@ package nl.andrewlalis.erme.control.diagram; +import nl.andrewlalis.erme.model.Attribute; import nl.andrewlalis.erme.model.MappingModel; import nl.andrewlalis.erme.model.Relation; import nl.andrewlalis.erme.view.DiagramPanel; @@ -25,7 +26,7 @@ public class DiagramMouseListener extends MouseAdapter { * - If the click occurs within at least one relation, select the first one, * and deselect all others if CTRL is not held down. * - If the user did a right-click, try to open a popup menu with some - * possible actions. + * possible actions, based on what entity we're over. * @param e The mouse event. */ @Override @@ -40,15 +41,24 @@ public class DiagramMouseListener extends MouseAdapter { final boolean isShiftDown = (e.getModifiers() & ActionEvent.SHIFT_MASK) == ActionEvent.SHIFT_MASK; MappingModel model = this.diagramPanel.getModel(); - if (!isShiftDown && !isCtrlDown) {// A simple click anywhere should reset selection. - model.getRelations().forEach(r -> r.setSelected(false)); + if (!isShiftDown && !isCtrlDown) {// A simple left-click anywhere should reset selection. + model.getSelectionModel().clearSelection(); } if (!isShiftDown) {// If the user clicked or CTRL+clicked, try and select the relation they clicked on. for (Relation r : model.getRelations()) { if (r.getViewModel().getBounds(g).contains(modelX, modelY)) { - r.setSelected(!r.isSelected()); - break; + boolean anyAttributeSelected = false; + for (Attribute a : r.getAttributes()) { + if (a.getViewModel().getBounds(g).contains(modelX, modelY)) { + model.getSelectionModel().toggle(a); + } + anyAttributeSelected = anyAttributeSelected || a.isSelected(); + } + if (!anyAttributeSelected) { + model.getSelectionModel().toggle(r); + break; + } } } } @@ -77,12 +87,11 @@ public class DiagramMouseListener extends MouseAdapter { MappingModel model = this.diagramPanel.getModel(); if (isShiftDown) { - System.out.println(e); this.diagramPanel.translate(-dx, -dy); this.diagramPanel.repaint(); } else { for (Relation r : model.getRelations()) { - if (r.isSelected()) { + if (model.getSelectionModel().isSelected(r)) { r.setPosition(new Point(r.getPosition().x - dx, r.getPosition().y - dy)); changed = true; } diff --git a/src/main/java/nl/andrewlalis/erme/model/Attribute.java b/src/main/java/nl/andrewlalis/erme/model/Attribute.java index d2f73f7..21f763b 100644 --- a/src/main/java/nl/andrewlalis/erme/model/Attribute.java +++ b/src/main/java/nl/andrewlalis/erme/model/Attribute.java @@ -13,6 +13,7 @@ public class Attribute { private final Relation relation; private AttributeType type; private String name; + private Attribute reference; private transient AttributeViewModel viewModel; @@ -20,6 +21,7 @@ public class Attribute { this.relation = relation; this.type = type; this.name = name; + this.reference = null; } public void setType(AttributeType type) { @@ -31,6 +33,14 @@ public class Attribute { this.name = name; this.relation.getModel().fireChangedEvent(); } + + public boolean hasReference() { + return this.reference != null; + } + + public void setReference(Attribute attribute) { + this.reference = attribute; + } public AttributeViewModel getViewModel() { if (this.viewModel == null) { @@ -39,6 +49,10 @@ public class Attribute { return this.viewModel; } + public boolean isSelected() { + return this.relation.getModel().getSelectionModel().isSelected(this); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/nl/andrewlalis/erme/model/ForeignKeyAttribute.java b/src/main/java/nl/andrewlalis/erme/model/ForeignKeyAttribute.java deleted file mode 100644 index 2b9b011..0000000 --- a/src/main/java/nl/andrewlalis/erme/model/ForeignKeyAttribute.java +++ /dev/null @@ -1,39 +0,0 @@ -package nl.andrewlalis.erme.model; - -import lombok.Getter; - -@Getter -public class ForeignKeyAttribute extends Attribute { - private Attribute reference; - - public ForeignKeyAttribute(Relation relation, AttributeType type, String name, Attribute reference) { - super(relation, type, name); - this.reference = reference; - } - - public ForeignKeyAttribute(Relation relation, AttributeType type, String name, String referencedRelationName, String referencedAttributeName) { - this(relation, type, name, relation.getModel().findAttribute(referencedRelationName, referencedAttributeName)); - if (this.getReference() == null) { - throw new IllegalArgumentException("Unknown attribute name."); - } - } - - public void setReference(Attribute reference) { - this.reference = reference; - this.getRelation().getModel().fireChangedEvent(); - } - - public String getFullReferenceName() { - return this.getReference().getRelation().getName() + "." + this.getReference().getName(); - } - - @Override - public String toString() { - return super.toString() + "->" + this.getFullReferenceName(); - } - - @Override - public ForeignKeyAttribute copy(Relation newRelation) { - return new ForeignKeyAttribute(newRelation, this.getType(), this.getName(), this.getReference()); - } -} diff --git a/src/main/java/nl/andrewlalis/erme/model/MappingModel.java b/src/main/java/nl/andrewlalis/erme/model/MappingModel.java index 7b34f62..2c93cd0 100644 --- a/src/main/java/nl/andrewlalis/erme/model/MappingModel.java +++ b/src/main/java/nl/andrewlalis/erme/model/MappingModel.java @@ -10,9 +10,7 @@ import nl.andrewlalis.erme.view.view_models.MappingModelViewModel; import nl.andrewlalis.erme.view.view_models.ViewModel; import java.awt.*; -import java.util.List; import java.util.*; -import java.util.stream.Collectors; /** * This model contains all the information about a single mapping diagram, @@ -33,10 +31,13 @@ public class MappingModel implements Viewable { @Getter @Setter private transient boolean referenceVisualizationEnabled = false; + @Getter + private transient final SelectionModel selectionModel; public MappingModel() { this.relations = new HashSet<>(); this.changeListeners = new HashSet<>(); + this.selectionModel = new SelectionModel(this); } public void addRelation(Relation r) { @@ -51,14 +52,6 @@ public class MappingModel implements Viewable { } } - /** - * Gets the list of relations which are currently selected. - * @return The list of relations which are selected. - */ - public List getSelectedRelations() { - return this.relations.stream().filter(Relation::isSelected).collect(Collectors.toList()); - } - /** * Finds an attribute in this model, or returns null otherwise. * @param relationName The name of the relation the attribute is in. @@ -86,11 +79,8 @@ public class MappingModel implements Viewable { for (Relation r : this.getRelations()) { Set removalSet = new HashSet<>(); for (Attribute a : r.getAttributes()) { - if (a instanceof ForeignKeyAttribute) { - ForeignKeyAttribute fkA = (ForeignKeyAttribute) a; - if (fkA.getReference().equals(referenced)) { - removalSet.add(fkA); - } + if (a.hasReference() && a.getReference().equals(referenced)) { + removalSet.add(a); } } removalSet.forEach(r::removeAttribute); @@ -196,12 +186,11 @@ public class MappingModel implements Viewable { ObjectNode attributeNode = mapper.createObjectNode() .put("name", a.getName()) .put("type", a.getType().name()); - if (a instanceof ForeignKeyAttribute) { - ForeignKeyAttribute fk = (ForeignKeyAttribute) a; + if (a.hasReference()) { ObjectNode referenceNode = mapper.createObjectNode() - .put("relation", fk.getReference().getRelation().getName()) - .put("attribute", fk.getReference().getName()); - attributeNode.set("references", referenceNode); + .put("relation", a.getReference().getRelation().getName()) + .put("attribute", a.getReference().getName()); + attributeNode.set("reference", referenceNode); } attributesArray.add(attributeNode); } @@ -245,7 +234,8 @@ public class MappingModel implements Viewable { Attribute referencedAttribute = model.findAttribute(referencedRelation, referencedName); if (referencedAttribute == null) throw new IllegalArgumentException("Foreign key referenced unknown attribute."); if (!references.containsKey(referencedAttribute)) { - ForeignKeyAttribute fk = new ForeignKeyAttribute(attribute.getRelation(), attribute.getType(), attribute.getName(), referencedAttribute); + Attribute fk = new Attribute(attribute.getRelation(), attribute.getType(), attribute.getName()); + fk.setReference(referencedAttribute); attribute.getRelation().removeAttribute(attribute); attribute.getRelation().addAttribute(fk); references.remove(attribute); @@ -270,8 +260,8 @@ public class MappingModel implements Viewable { Map references = new HashMap<>(); for (JsonNode r : node.withArray("relations")) { for (JsonNode a : r.withArray("attributes")) { - if (a.has("references") && a.get("references").isObject()) { - ObjectNode referenceNode = (ObjectNode) a.get("references"); + if (a.has("reference") && a.get("reference").isObject()) { + ObjectNode referenceNode = (ObjectNode) a.get("reference"); String attributeName = a.get("name").asText(); String relationName = r.get("name").asText(); Attribute attribute = model.findAttribute(relationName, attributeName); diff --git a/src/main/java/nl/andrewlalis/erme/model/Relation.java b/src/main/java/nl/andrewlalis/erme/model/Relation.java index 4a48350..49dc703 100644 --- a/src/main/java/nl/andrewlalis/erme/model/Relation.java +++ b/src/main/java/nl/andrewlalis/erme/model/Relation.java @@ -20,7 +20,6 @@ public class Relation implements Viewable, Comparable { private String name; private final List attributes; - private transient boolean selected; private transient RelationViewModel viewModel; public Relation(MappingModel model, Point position, String name, List attributes) { @@ -42,10 +41,6 @@ public class Relation implements Viewable, Comparable { this.name = name; } - public void setSelected(boolean selected) { - this.selected = selected; - } - public void addAttribute(Attribute attribute) { this.attributes.add(attribute); this.model.fireChangedEvent(); @@ -63,6 +58,10 @@ public class Relation implements Viewable, Comparable { } } + public boolean isSelected() { + return this.model.getSelectionModel().isSelected(this); + } + @Override public ViewModel getViewModel() { if (this.viewModel == null) { diff --git a/src/main/java/nl/andrewlalis/erme/model/SelectionModel.java b/src/main/java/nl/andrewlalis/erme/model/SelectionModel.java new file mode 100644 index 0000000..298fb2d --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/model/SelectionModel.java @@ -0,0 +1,75 @@ +package nl.andrewlalis.erme.model; + +import java.util.*; + +/** + * A model that keeps track of the application's set of selected elements. + */ +public class SelectionModel { + private final MappingModel model; + private final Set selectedRelations; + private final Set selectedAttributes; + + public SelectionModel(MappingModel model) { + this.model = model; + this.selectedRelations = new HashSet<>(); + this.selectedAttributes = new HashSet<>(); + } + + public boolean isSelected(Relation relation) { + return this.selectedRelations.contains(relation); + } + + public boolean isSelected(Attribute attribute) { + return this.selectedAttributes.contains(attribute); + } + + public List getSelectedRelations() { + return new ArrayList<>(this.selectedRelations); + } + + public List getSelectedAttributes() { + return new ArrayList<>(this.selectedAttributes); + } + + public void select(Relation relation) { + this.selectedRelations.add(relation); + } + + public void selectAll(Collection relations) { + this.selectedRelations.addAll(relations); + } + + public void select(Attribute attribute) { + this.selectedAttributes.add(attribute); + } + + public void toggle(Relation relation) { + if (selectedRelations.contains(relation)) { + selectedRelations.remove(relation); + } else { + selectedRelations.add(relation); + } + } + + public void toggle(Attribute attribute) { + if (selectedAttributes.contains(attribute)) { + selectedAttributes.remove(attribute); + } else { + selectedAttributes.add(attribute); + } + } + + public void clearSelection() { + this.selectedRelations.clear(); + this.selectedAttributes.clear(); + } + + public void clearRelations() { + this.selectedRelations.clear(); + } + + public void clearAttributes() { + this.selectedAttributes.clear(); + } +} diff --git a/src/main/java/nl/andrewlalis/erme/view/DiagramPopupMenu.java b/src/main/java/nl/andrewlalis/erme/view/DiagramPopupMenu.java index c980f68..2772150 100644 --- a/src/main/java/nl/andrewlalis/erme/view/DiagramPopupMenu.java +++ b/src/main/java/nl/andrewlalis/erme/view/DiagramPopupMenu.java @@ -2,7 +2,9 @@ package nl.andrewlalis.erme.view; import nl.andrewlalis.erme.control.actions.edits.AddAttributeAction; import nl.andrewlalis.erme.control.actions.edits.AddRelationAction; +import nl.andrewlalis.erme.control.actions.edits.RemoveAttributeAction; import nl.andrewlalis.erme.control.actions.edits.RemoveRelationAction; +import nl.andrewlalis.erme.model.Attribute; import nl.andrewlalis.erme.model.Relation; import javax.swing.*; @@ -10,19 +12,19 @@ import java.util.List; public class DiagramPopupMenu extends JPopupMenu { public DiagramPopupMenu(DiagramPanel diagramPanel) { - List selectedRelations = diagramPanel.getModel().getSelectedRelations(); - if (selectedRelations.size() == 0) { + List selectedRelations = diagramPanel.getModel().getSelectionModel().getSelectedRelations(); + List selectedAttributes = diagramPanel.getModel().getSelectionModel().getSelectedAttributes(); + if (selectedRelations.isEmpty() && selectedAttributes.isEmpty()) { this.add(new AddRelationAction(diagramPanel)); } - if (selectedRelations.size() > 0) { + if (selectedRelations.size() > 0 && selectedAttributes.isEmpty()) { this.add(new RemoveRelationAction(diagramPanel)); } - if (selectedRelations.size() == 1) { - Relation relation = selectedRelations.get(0); + if (selectedRelations.size() == 1 && selectedAttributes.isEmpty()) { this.add(new AddAttributeAction(diagramPanel)); - if (!relation.getAttributes().isEmpty()) { - this.add(new RemoveRelationAction(diagramPanel)); - } + } + if (!selectedAttributes.isEmpty() && selectedRelations.isEmpty()) { + this.add(new RemoveAttributeAction(diagramPanel)); } } } diff --git a/src/main/java/nl/andrewlalis/erme/view/EditAttributePopupDialog.java b/src/main/java/nl/andrewlalis/erme/view/EditAttributePopupDialog.java new file mode 100644 index 0000000..12a7dba --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/view/EditAttributePopupDialog.java @@ -0,0 +1,96 @@ +package nl.andrewlalis.erme.view; + +import nl.andrewlalis.erme.model.Attribute; +import nl.andrewlalis.erme.model.AttributeType; +import nl.andrewlalis.erme.model.Relation; + +import javax.swing.*; +import java.awt.*; + +/** + * A popup that's shown when creating or editing an attribute of a relation. + */ +public class EditAttributePopupDialog extends JDialog { + private final Relation relation; + private final Attribute attribute; + + private final JTextField nameField; + private final JComboBox attributeTypeComboBox; + private final JSpinner orderSpinner; + + public EditAttributePopupDialog(Frame owner, Relation relation, Attribute attribute) { + super(owner, "Edit Attribute", true); + this.relation = relation; + this.attribute = attribute; + JPanel mainPanel = new JPanel(new GridBagLayout()); + GridBagConstraints gc = new GridBagConstraints(); + gc.ipadx = 2; + gc.ipady = 2; + gc.gridx = 0; + gc.gridy = 0; + gc.anchor = GridBagConstraints.LINE_START; + mainPanel.add(new JLabel("Name"), gc); + gc.gridx = 1; + gc.anchor = GridBagConstraints.LINE_END; + this.nameField = new JTextField(20); + mainPanel.add(nameField, gc); + gc.gridx = 0; + gc.gridy++; + gc.anchor = GridBagConstraints.LINE_START; + mainPanel.add(new JLabel("Type"), gc); + gc.gridx = 1; + gc.anchor = GridBagConstraints.LINE_END; + this.attributeTypeComboBox = new JComboBox<>(AttributeType.values()); + mainPanel.add(attributeTypeComboBox, gc); + gc.gridx = 0; + gc.gridy++; + gc.anchor = GridBagConstraints.LINE_START; + mainPanel.add(new JLabel("Order"), gc); + gc.gridx = 1; + gc.anchor = GridBagConstraints.LINE_END; + this.orderSpinner = new JSpinner(new SpinnerNumberModel(1, 1, relation.getAttributes().size() + 1, 1)); + mainPanel.add(orderSpinner, gc); + + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + JButton okayButton = new JButton("Okay"); + okayButton.addActionListener(e -> this.submit()); + JButton cancelButton = new JButton("Cancel"); + cancelButton.addActionListener(e -> this.dispose()); + buttonPanel.add(okayButton); + buttonPanel.add(cancelButton); + gc.gridx = 0; + gc.gridy++; + gc.gridwidth = 2; + mainPanel.add(buttonPanel, gc); + + if (this.attribute != null) { + this.nameField.setText(this.attribute.getName()); + this.attributeTypeComboBox.setSelectedItem(this.attribute.getType()); + this.orderSpinner.setValue(this.relation.getAttributes().indexOf(this.attribute)); + } else { + this.orderSpinner.setValue(this.relation.getAttributes().size() + 1); + } + + this.setContentPane(mainPanel); + this.pack(); + this.setLocationRelativeTo(owner); + } + + private void submit() { + String name = this.nameField.getText().trim(); + AttributeType type = (AttributeType) this.attributeTypeComboBox.getSelectedItem(); + int order = (int) this.orderSpinner.getValue(); + if (name.isEmpty()) { + JOptionPane.showMessageDialog(this, "The attribute must have a name.", "Missing Name", JOptionPane.WARNING_MESSAGE); + return; + } + if (this.attribute != null) { + this.attribute.setName(name); + this.attribute.setType(type); + } else { + Attribute a = new Attribute(this.relation, type, name); + this.relation.addAttribute(a, order - 1); + } + this.dispose(); + } +} diff --git a/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java b/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java index 8be07c0..dc0abf2 100644 --- a/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java +++ b/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java @@ -3,6 +3,7 @@ package nl.andrewlalis.erme.view; import nl.andrewlalis.erme.control.actions.*; import nl.andrewlalis.erme.control.actions.edits.AddAttributeAction; import nl.andrewlalis.erme.control.actions.edits.AddRelationAction; +import nl.andrewlalis.erme.control.actions.edits.RemoveAttributeAction; import nl.andrewlalis.erme.control.actions.edits.RemoveRelationAction; import javax.swing.*; @@ -35,7 +36,7 @@ public class EditorMenuBar extends JMenuBar { menu.add(new AddRelationAction(diagramPanel)); menu.add(new RemoveRelationAction(diagramPanel)); menu.add(new AddAttributeAction(diagramPanel)); - menu.add(new RemoveRelationAction(diagramPanel)); + menu.add(new RemoveAttributeAction(diagramPanel)); menu.add(new AutoPositionAction(diagramPanel)); return menu; } diff --git a/src/main/java/nl/andrewlalis/erme/view/view_models/AttributeViewModel.java b/src/main/java/nl/andrewlalis/erme/view/view_models/AttributeViewModel.java index ca45ed2..8d8a880 100644 --- a/src/main/java/nl/andrewlalis/erme/view/view_models/AttributeViewModel.java +++ b/src/main/java/nl/andrewlalis/erme/view/view_models/AttributeViewModel.java @@ -2,7 +2,6 @@ package nl.andrewlalis.erme.view.view_models; import nl.andrewlalis.erme.model.Attribute; import nl.andrewlalis.erme.model.AttributeType; -import nl.andrewlalis.erme.model.ForeignKeyAttribute; import java.awt.*; import java.awt.font.TextAttribute; @@ -16,6 +15,7 @@ public class AttributeViewModel implements ViewModel { public static final int PADDING_X = 5; public static final int PADDING_Y = 5; public static final Color BACKGROUND_COLOR = new Color(192, 192, 192, 127); + public static final Color SELECTED_COLOR = new Color(148, 255, 176, 127); public static final Color FONT_COLOR = Color.BLACK; public static final float FK_FONT_SIZE = 11.0f; private static final float LOLCAT_SAT = 0.75f; @@ -35,16 +35,16 @@ public class AttributeViewModel implements ViewModel { g.setColor(FONT_COLOR); g.drawRect(r.x, r.y, r.width, r.height); g.drawString(as.getIterator(), r.x + PADDING_X, r.y + (r.height - PADDING_Y)); - if (this.attribute instanceof ForeignKeyAttribute) { - ForeignKeyAttribute fkAttribute = (ForeignKeyAttribute) this.attribute; + if (this.attribute.hasReference() && !this.attribute.getRelation().getModel().isReferenceVisualizationEnabled()) { Font originalFont = g.getFont(); g.setFont(g.getFont().deriveFont(Font.ITALIC, FK_FONT_SIZE)); - g.drawString(fkAttribute.getFullReferenceName(), r.x + PADDING_X, r.y - PADDING_Y); + g.drawString(getFullReferenceName(), r.x + PADDING_X, r.y - PADDING_Y); g.setFont(originalFont); } } private Color getBackgroundColor(int x, int y, Graphics2D g) { + if (attribute.isSelected()) return SELECTED_COLOR; if (!attribute.getRelation().getModel().isLolcatEnabled()) return BACKGROUND_COLOR; Point offset = g.getClipBounds().getLocation(); g.translate(offset.x, offset.y); @@ -82,11 +82,10 @@ public class AttributeViewModel implements ViewModel { Rectangle2D nameRect = g.getFontMetrics().getStringBounds(as.getIterator(), 0, this.attribute.getName().length(), g); int width = (int) nameRect.getWidth() + (2 * PADDING_X); int height = (int) nameRect.getHeight() + (2 * PADDING_Y); - if (this.attribute instanceof ForeignKeyAttribute) { - ForeignKeyAttribute fkAttribute = (ForeignKeyAttribute) this.attribute; + if (this.attribute.hasReference()) { Font originalFont = g.getFont(); g.setFont(g.getFont().deriveFont(Font.ITALIC, FK_FONT_SIZE)); - Rectangle referenceNameBounds = g.getFontMetrics().getStringBounds(fkAttribute.getFullReferenceName(), g).getBounds(); + Rectangle referenceNameBounds = g.getFontMetrics().getStringBounds(getFullReferenceName(), g).getBounds(); g.setFont(originalFont); width = Math.max(width, referenceNameBounds.width + (2 * PADDING_X)); } @@ -103,4 +102,9 @@ public class AttributeViewModel implements ViewModel { } return as; } + + private String getFullReferenceName() { + if (!this.attribute.hasReference()) return ""; + return this.attribute.getReference().getRelation().getName() + "." + this.attribute.getReference().getName(); + } } diff --git a/src/main/java/nl/andrewlalis/erme/view/view_models/MappingModelViewModel.java b/src/main/java/nl/andrewlalis/erme/view/view_models/MappingModelViewModel.java index c54bd7d..c714c02 100644 --- a/src/main/java/nl/andrewlalis/erme/view/view_models/MappingModelViewModel.java +++ b/src/main/java/nl/andrewlalis/erme/view/view_models/MappingModelViewModel.java @@ -1,7 +1,6 @@ package nl.andrewlalis.erme.view.view_models; import nl.andrewlalis.erme.model.Attribute; -import nl.andrewlalis.erme.model.ForeignKeyAttribute; import nl.andrewlalis.erme.model.MappingModel; import nl.andrewlalis.erme.model.Relation; @@ -32,13 +31,12 @@ public class MappingModelViewModel implements ViewModel { g2.setStroke(dashedStroke); for (Relation r : this.model.getRelations()) { for (Attribute a : r.getAttributes()) { - if (a instanceof ForeignKeyAttribute) { - ForeignKeyAttribute fk = (ForeignKeyAttribute) a; + if (a.hasReference()) { // Generate a random HSB color for the line, seeded using the referenced attribute's hash code. - Random random = new Random(fk.getReference().hashCode()); + Random random = new Random(a.getReference().hashCode()); g2.setColor(Color.getHSBColor(random.nextFloat(), 1.0f, 0.8f)); - Rectangle sourceBounds = fk.getViewModel().getBounds(g); - Rectangle targetBounds = fk.getReference().getViewModel().getBounds(g); + Rectangle sourceBounds = a.getViewModel().getBounds(g); + Rectangle targetBounds = a.getReference().getViewModel().getBounds(g); Point sourcePoint = new Point(sourceBounds.x + sourceBounds.width / 2, sourceBounds.y + 3 * targetBounds.height / 4); Point targetPoint = new Point(targetBounds.x + targetBounds.width / 2, targetBounds.y + 3 * targetBounds.height / 4); g2.drawLine(sourcePoint.x, sourcePoint.y, targetPoint.x, targetPoint.y);