Added a selection model, and attribute selection, and cleaned up foreign key referencing.

This commit is contained in:
Andrew Lalis 2021-10-31 10:47:07 +01:00
parent 1019654e76
commit cea15d872b
17 changed files with 347 additions and 191 deletions

View File

@ -124,10 +124,10 @@ public class ExportToImageAction extends DiagramPanelAction {
// Render the model. // Render the model.
boolean lolcat = model.isLolcatEnabled(); // save previous lolcat mode boolean lolcat = model.isLolcatEnabled(); // save previous lolcat mode
model.setLolcatEnabled(false); model.setLolcatEnabled(false);
List<Relation> selectedRelations = model.getSelectedRelations(); List<Relation> selectedRelations = model.getSelectionModel().getSelectedRelations();
model.getSelectedRelations().forEach(r -> r.setSelected(false)); model.getSelectionModel().clearSelection();
new MappingModelViewModel(model).draw(g2d); 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 model.setLolcatEnabled(lolcat); // revert previous lolcat mode
// Revert to the normal image space, and render a watermark. // Revert to the normal image space, and render a watermark.

View File

@ -1,6 +1,9 @@
package nl.andrewlalis.erme.control.actions; 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 nl.andrewlalis.erme.view.DiagramPanel;
import java.awt.*; import java.awt.*;
@ -22,7 +25,9 @@ public class LoadSampleModelAction extends DiagramPanelAction {
Relation r1 = new Relation(model, new Point(50, 100), "Airplane"); 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.ID_KEY, "id"));
r1.addAttribute(new Attribute(r1, AttributeType.PLAIN, "purchasedAt")); 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); model.addRelation(r1);
getDiagramPanel().setModel(model); getDiagramPanel().setModel(model);
} }

View File

@ -3,6 +3,7 @@ package nl.andrewlalis.erme.control.actions.edits;
import nl.andrewlalis.erme.control.actions.DiagramPanelAction; import nl.andrewlalis.erme.control.actions.DiagramPanelAction;
import nl.andrewlalis.erme.model.*; import nl.andrewlalis.erme.model.*;
import nl.andrewlalis.erme.view.DiagramPanel; import nl.andrewlalis.erme.view.DiagramPanel;
import nl.andrewlalis.erme.view.EditAttributePopupDialog;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@ -23,7 +24,7 @@ public class AddAttributeAction extends DiagramPanelAction {
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
DiagramPanel dp = getDiagramPanel(); DiagramPanel dp = getDiagramPanel();
MappingModel model = dp.getModel(); MappingModel model = dp.getModel();
List<Relation> selectedRelations = model.getSelectedRelations(); List<Relation> selectedRelations = model.getSelectionModel().getSelectedRelations();
if (selectedRelations.size() != 1) { if (selectedRelations.size() != 1) {
JOptionPane.showMessageDialog( JOptionPane.showMessageDialog(
dp, dp,
@ -34,72 +35,74 @@ public class AddAttributeAction extends DiagramPanelAction {
return; return;
} }
Relation r = selectedRelations.get(0); Relation r = selectedRelations.get(0);
Attribute createdAttribute; EditAttributePopupDialog popup = new EditAttributePopupDialog((JFrame) SwingUtilities.getWindowAncestor(dp), r, null);
String name = JOptionPane.showInputDialog(dp, "Enter the name of the attribute.", "Attribute Name", JOptionPane.PLAIN_MESSAGE); popup.setVisible(true);
if (name == null) return; // Attribute createdAttribute;
Integer index = (Integer) JOptionPane.showInputDialog( // String name = JOptionPane.showInputDialog(dp, "Enter the name of the attribute.", "Attribute Name", JOptionPane.PLAIN_MESSAGE);
dp, // if (name == null) return;
"Select the index to insert this attribute at.", // Integer index = (Integer) JOptionPane.showInputDialog(
"Attribute Index", // dp,
JOptionPane.PLAIN_MESSAGE, // "Select the index to insert this attribute at.",
null, // "Attribute Index",
Stream.iterate(0, n -> n + 1).limit(r.getAttributes().size() + 1).toArray(), // JOptionPane.PLAIN_MESSAGE,
r.getAttributes().size() // null,
); // Stream.iterate(0, n -> n + 1).limit(r.getAttributes().size() + 1).toArray(),
if (index == null) return; // r.getAttributes().size()
AttributeType type = (AttributeType) JOptionPane.showInputDialog( // );
dp, // if (index == null) return;
"Select the type this attribute is.", // AttributeType type = (AttributeType) JOptionPane.showInputDialog(
"Attribute Type", // dp,
JOptionPane.PLAIN_MESSAGE, // "Select the type this attribute is.",
null, // "Attribute Type",
AttributeType.values(), // JOptionPane.PLAIN_MESSAGE,
AttributeType.PLAIN // null,
); // AttributeType.values(),
if (type == null) return; // AttributeType.PLAIN
boolean shouldUseForeignKey = ((String) JOptionPane.showInputDialog( // );
dp, // if (type == null) return;
"Is this attribute a foreign key?", // boolean shouldUseForeignKey = ((String) JOptionPane.showInputDialog(
"Foreign Key", // dp,
JOptionPane.PLAIN_MESSAGE, // "Is this attribute a foreign key?",
null, // "Foreign Key",
new String[]{"Yes", "No"}, // JOptionPane.PLAIN_MESSAGE,
"No" // null,
)).equalsIgnoreCase("yes"); // new String[]{"Yes", "No"},
if (shouldUseForeignKey) { // "No"
if (model.getRelations().size() < 2) { // )).equalsIgnoreCase("yes");
JOptionPane.showMessageDialog(dp, "There should be at least 2 relations present in the model.", "Not Enough Relations", JOptionPane.WARNING_MESSAGE); // if (shouldUseForeignKey) {
return; // 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);
Relation fkRelation = (Relation) JOptionPane.showInputDialog( // return;
dp, // }
"Select the relation that this foreign key references.", // Relation fkRelation = (Relation) JOptionPane.showInputDialog(
"Foreign Key Relation Reference", // dp,
JOptionPane.PLAIN_MESSAGE, // "Select the relation that this foreign key references.",
null, // "Foreign Key Relation Reference",
model.getRelations().toArray(new Relation[0]), // JOptionPane.PLAIN_MESSAGE,
model.getRelations().stream().findFirst().orElse(null) // null,
); // model.getRelations().toArray(new Relation[0]),
if (fkRelation == null) return; // model.getRelations().stream().findFirst().orElse(null)
List<Attribute> eligibleAttributes = fkRelation.getAttributes(); // );
if (eligibleAttributes.isEmpty()) { // if (fkRelation == null) return;
JOptionPane.showMessageDialog(dp, "There are no referencable attributes in the selected relation.", "No Referencable Attributes", JOptionPane.WARNING_MESSAGE); // List<Attribute> eligibleAttributes = fkRelation.getAttributes();
return; // if (eligibleAttributes.isEmpty()) {
} // JOptionPane.showMessageDialog(dp, "There are no referencable attributes in the selected relation.", "No Referencable Attributes", JOptionPane.WARNING_MESSAGE);
Attribute fkAttribute = (Attribute) JOptionPane.showInputDialog( // return;
dp, // }
"Select the attribute that this foreign key references.", // Attribute fkAttribute = (Attribute) JOptionPane.showInputDialog(
"Foreign Key Attribute Reference", // dp,
JOptionPane.PLAIN_MESSAGE, // "Select the attribute that this foreign key references.",
null, // "Foreign Key Attribute Reference",
eligibleAttributes.toArray(new Attribute[0]), // JOptionPane.PLAIN_MESSAGE,
eligibleAttributes.get(0) // null,
); // eligibleAttributes.toArray(new Attribute[0]),
if (fkAttribute == null) return; // eligibleAttributes.get(0)
createdAttribute = new ForeignKeyAttribute(r, type, name, fkAttribute); // );
} else { // if (fkAttribute == null) return;
createdAttribute = new Attribute(r, type, name); // createdAttribute = new ForeignKeyAttribute(r, type, name, fkAttribute);
} // } else {
r.addAttribute(createdAttribute, index); // createdAttribute = new Attribute(r, type, name);
// }
// r.addAttribute(createdAttribute, index);
} }
} }

View File

@ -43,9 +43,9 @@ public class AddRelationAction extends DiagramPanelAction {
p = new Point(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2); p = new Point(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2);
} }
Relation r = new Relation(model, p, name); Relation r = new Relation(model, p, name);
model.getSelectedRelations().forEach(rl -> rl.setSelected(false));
r.setSelected(true);
model.addRelation(r); model.addRelation(r);
model.getSelectionModel().clearSelection();
model.getSelectionModel().select(r);
if (isFirstRelation) { if (isFirstRelation) {
model.normalizeRelationPositions(); model.normalizeRelationPositions();
dp.centerModel(); dp.centerModel();

View File

@ -2,7 +2,6 @@ package nl.andrewlalis.erme.control.actions.edits;
import nl.andrewlalis.erme.control.actions.DiagramPanelAction; import nl.andrewlalis.erme.control.actions.DiagramPanelAction;
import nl.andrewlalis.erme.model.Attribute; import nl.andrewlalis.erme.model.Attribute;
import nl.andrewlalis.erme.model.Relation;
import nl.andrewlalis.erme.view.DiagramPanel; import nl.andrewlalis.erme.view.DiagramPanel;
import javax.swing.*; import javax.swing.*;
@ -20,28 +19,22 @@ public class RemoveAttributeAction extends DiagramPanelAction {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
List<Relation> selectedRelations = getDiagramPanel().getModel().getSelectedRelations(); List<Attribute> selectedAttributes = getDiagramPanel().getModel().getSelectionModel().getSelectedAttributes();
if (selectedRelations.size() != 1 || selectedRelations.get(0).getAttributes().isEmpty()) { if (selectedAttributes.isEmpty()) {
JOptionPane.showMessageDialog( JOptionPane.showMessageDialog(
getDiagramPanel(), getDiagramPanel(),
"A single relation with at least one attribute must be selected to remove an attribute.", "At least one attribute must be selected to remove.",
"Single Relation With Attribute Required", "Select Attributes to Remove Them",
JOptionPane.WARNING_MESSAGE JOptionPane.WARNING_MESSAGE
); );
return; return;
} }
Relation r = selectedRelations.get(0); int choice = JOptionPane.showConfirmDialog(getDiagramPanel(), "Are you sure you want to remove these attributes?", "Confirm", JOptionPane.OK_CANCEL_OPTION);
Attribute attribute = (Attribute) JOptionPane.showInputDialog( if (choice == JOptionPane.YES_OPTION) {
getDiagramPanel(), for (Attribute a : selectedAttributes) {
"Select the attribute to remove.", a.getRelation().removeAttribute(a);
"Select Attribute", }
JOptionPane.PLAIN_MESSAGE, getDiagramPanel().getModel().getSelectionModel().clearAttributes();
null,
r.getAttributes().toArray(new Attribute[0]),
r.getAttributes().get(0)
);
if (attribute != null) {
r.removeAttribute(attribute);
} }
} }
} }

View File

@ -9,6 +9,7 @@ import javax.swing.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.InputEvent; import java.awt.event.InputEvent;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.util.List;
public class RemoveRelationAction extends DiagramPanelAction { public class RemoveRelationAction extends DiagramPanelAction {
public RemoveRelationAction(DiagramPanel diagramPanel) { public RemoveRelationAction(DiagramPanel diagramPanel) {
@ -20,7 +21,8 @@ public class RemoveRelationAction extends DiagramPanelAction {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
MappingModel model = getDiagramPanel().getModel(); MappingModel model = getDiagramPanel().getModel();
if (model.getSelectedRelations().isEmpty()) { List<Relation> selectedRelations = model.getSelectionModel().getSelectedRelations();
if (selectedRelations.isEmpty()) {
JOptionPane.showMessageDialog( JOptionPane.showMessageDialog(
getDiagramPanel(), getDiagramPanel(),
"No relations selected. Select at least one relation to remove.", "No relations selected. Select at least one relation to remove.",
@ -29,8 +31,12 @@ public class RemoveRelationAction extends DiagramPanelAction {
); );
return; return;
} }
for (Relation r : model.getSelectedRelations()) { 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.removeRelation(r);
} }
model.getSelectionModel().clearRelations();
}
} }
} }

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.erme.control.diagram; package nl.andrewlalis.erme.control.diagram;
import nl.andrewlalis.erme.model.Attribute;
import nl.andrewlalis.erme.model.MappingModel; import nl.andrewlalis.erme.model.MappingModel;
import nl.andrewlalis.erme.model.Relation; import nl.andrewlalis.erme.model.Relation;
import nl.andrewlalis.erme.view.DiagramPanel; 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, * - If the click occurs within at least one relation, select the first one,
* and deselect all others if CTRL is not held down. * 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 * - 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. * @param e The mouse event.
*/ */
@Override @Override
@ -40,18 +41,27 @@ public class DiagramMouseListener extends MouseAdapter {
final boolean isShiftDown = (e.getModifiers() & ActionEvent.SHIFT_MASK) == ActionEvent.SHIFT_MASK; final boolean isShiftDown = (e.getModifiers() & ActionEvent.SHIFT_MASK) == ActionEvent.SHIFT_MASK;
MappingModel model = this.diagramPanel.getModel(); MappingModel model = this.diagramPanel.getModel();
if (!isShiftDown && !isCtrlDown) {// A simple click anywhere should reset selection. if (!isShiftDown && !isCtrlDown) {// A simple left-click anywhere should reset selection.
model.getRelations().forEach(r -> r.setSelected(false)); model.getSelectionModel().clearSelection();
} }
if (!isShiftDown) {// If the user clicked or CTRL+clicked, try and select the relation they clicked on. if (!isShiftDown) {// If the user clicked or CTRL+clicked, try and select the relation they clicked on.
for (Relation r : model.getRelations()) { for (Relation r : model.getRelations()) {
if (r.getViewModel().getBounds(g).contains(modelX, modelY)) { if (r.getViewModel().getBounds(g).contains(modelX, modelY)) {
r.setSelected(!r.isSelected()); 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; break;
} }
} }
} }
}
// If the user right-clicked, show a popup menu. // If the user right-clicked, show a popup menu.
if (e.getButton() == MouseEvent.BUTTON3) { if (e.getButton() == MouseEvent.BUTTON3) {
@ -77,12 +87,11 @@ public class DiagramMouseListener extends MouseAdapter {
MappingModel model = this.diagramPanel.getModel(); MappingModel model = this.diagramPanel.getModel();
if (isShiftDown) { if (isShiftDown) {
System.out.println(e);
this.diagramPanel.translate(-dx, -dy); this.diagramPanel.translate(-dx, -dy);
this.diagramPanel.repaint(); this.diagramPanel.repaint();
} else { } else {
for (Relation r : model.getRelations()) { 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)); r.setPosition(new Point(r.getPosition().x - dx, r.getPosition().y - dy));
changed = true; changed = true;
} }

View File

@ -13,6 +13,7 @@ public class Attribute {
private final Relation relation; private final Relation relation;
private AttributeType type; private AttributeType type;
private String name; private String name;
private Attribute reference;
private transient AttributeViewModel viewModel; private transient AttributeViewModel viewModel;
@ -20,6 +21,7 @@ public class Attribute {
this.relation = relation; this.relation = relation;
this.type = type; this.type = type;
this.name = name; this.name = name;
this.reference = null;
} }
public void setType(AttributeType type) { public void setType(AttributeType type) {
@ -32,6 +34,14 @@ public class Attribute {
this.relation.getModel().fireChangedEvent(); this.relation.getModel().fireChangedEvent();
} }
public boolean hasReference() {
return this.reference != null;
}
public void setReference(Attribute attribute) {
this.reference = attribute;
}
public AttributeViewModel getViewModel() { public AttributeViewModel getViewModel() {
if (this.viewModel == null) { if (this.viewModel == null) {
this.viewModel = new AttributeViewModel(this); this.viewModel = new AttributeViewModel(this);
@ -39,6 +49,10 @@ public class Attribute {
return this.viewModel; return this.viewModel;
} }
public boolean isSelected() {
return this.relation.getModel().getSelectionModel().isSelected(this);
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

View File

@ -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());
}
}

View File

@ -10,9 +10,7 @@ import nl.andrewlalis.erme.view.view_models.MappingModelViewModel;
import nl.andrewlalis.erme.view.view_models.ViewModel; import nl.andrewlalis.erme.view.view_models.ViewModel;
import java.awt.*; import java.awt.*;
import java.util.List;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
/** /**
* This model contains all the information about a single mapping diagram, * This model contains all the information about a single mapping diagram,
@ -33,10 +31,13 @@ public class MappingModel implements Viewable {
@Getter @Getter
@Setter @Setter
private transient boolean referenceVisualizationEnabled = false; private transient boolean referenceVisualizationEnabled = false;
@Getter
private transient final SelectionModel selectionModel;
public MappingModel() { public MappingModel() {
this.relations = new HashSet<>(); this.relations = new HashSet<>();
this.changeListeners = new HashSet<>(); this.changeListeners = new HashSet<>();
this.selectionModel = new SelectionModel(this);
} }
public void addRelation(Relation r) { 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<Relation> getSelectedRelations() {
return this.relations.stream().filter(Relation::isSelected).collect(Collectors.toList());
}
/** /**
* Finds an attribute in this model, or returns null otherwise. * Finds an attribute in this model, or returns null otherwise.
* @param relationName The name of the relation the attribute is in. * @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()) { for (Relation r : this.getRelations()) {
Set<Attribute> removalSet = new HashSet<>(); Set<Attribute> removalSet = new HashSet<>();
for (Attribute a : r.getAttributes()) { for (Attribute a : r.getAttributes()) {
if (a instanceof ForeignKeyAttribute) { if (a.hasReference() && a.getReference().equals(referenced)) {
ForeignKeyAttribute fkA = (ForeignKeyAttribute) a; removalSet.add(a);
if (fkA.getReference().equals(referenced)) {
removalSet.add(fkA);
}
} }
} }
removalSet.forEach(r::removeAttribute); removalSet.forEach(r::removeAttribute);
@ -196,12 +186,11 @@ public class MappingModel implements Viewable {
ObjectNode attributeNode = mapper.createObjectNode() ObjectNode attributeNode = mapper.createObjectNode()
.put("name", a.getName()) .put("name", a.getName())
.put("type", a.getType().name()); .put("type", a.getType().name());
if (a instanceof ForeignKeyAttribute) { if (a.hasReference()) {
ForeignKeyAttribute fk = (ForeignKeyAttribute) a;
ObjectNode referenceNode = mapper.createObjectNode() ObjectNode referenceNode = mapper.createObjectNode()
.put("relation", fk.getReference().getRelation().getName()) .put("relation", a.getReference().getRelation().getName())
.put("attribute", fk.getReference().getName()); .put("attribute", a.getReference().getName());
attributeNode.set("references", referenceNode); attributeNode.set("reference", referenceNode);
} }
attributesArray.add(attributeNode); attributesArray.add(attributeNode);
} }
@ -245,7 +234,8 @@ public class MappingModel implements Viewable {
Attribute referencedAttribute = model.findAttribute(referencedRelation, referencedName); Attribute referencedAttribute = model.findAttribute(referencedRelation, referencedName);
if (referencedAttribute == null) throw new IllegalArgumentException("Foreign key referenced unknown attribute."); if (referencedAttribute == null) throw new IllegalArgumentException("Foreign key referenced unknown attribute.");
if (!references.containsKey(referencedAttribute)) { 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().removeAttribute(attribute);
attribute.getRelation().addAttribute(fk); attribute.getRelation().addAttribute(fk);
references.remove(attribute); references.remove(attribute);
@ -270,8 +260,8 @@ public class MappingModel implements Viewable {
Map<Attribute, ObjectNode> references = new HashMap<>(); Map<Attribute, ObjectNode> references = new HashMap<>();
for (JsonNode r : node.withArray("relations")) { for (JsonNode r : node.withArray("relations")) {
for (JsonNode a : r.withArray("attributes")) { for (JsonNode a : r.withArray("attributes")) {
if (a.has("references") && a.get("references").isObject()) { if (a.has("reference") && a.get("reference").isObject()) {
ObjectNode referenceNode = (ObjectNode) a.get("references"); ObjectNode referenceNode = (ObjectNode) a.get("reference");
String attributeName = a.get("name").asText(); String attributeName = a.get("name").asText();
String relationName = r.get("name").asText(); String relationName = r.get("name").asText();
Attribute attribute = model.findAttribute(relationName, attributeName); Attribute attribute = model.findAttribute(relationName, attributeName);

View File

@ -20,7 +20,6 @@ public class Relation implements Viewable, Comparable<Relation> {
private String name; private String name;
private final List<Attribute> attributes; private final List<Attribute> attributes;
private transient boolean selected;
private transient RelationViewModel viewModel; private transient RelationViewModel viewModel;
public Relation(MappingModel model, Point position, String name, List<Attribute> attributes) { public Relation(MappingModel model, Point position, String name, List<Attribute> attributes) {
@ -42,10 +41,6 @@ public class Relation implements Viewable, Comparable<Relation> {
this.name = name; this.name = name;
} }
public void setSelected(boolean selected) {
this.selected = selected;
}
public void addAttribute(Attribute attribute) { public void addAttribute(Attribute attribute) {
this.attributes.add(attribute); this.attributes.add(attribute);
this.model.fireChangedEvent(); this.model.fireChangedEvent();
@ -63,6 +58,10 @@ public class Relation implements Viewable, Comparable<Relation> {
} }
} }
public boolean isSelected() {
return this.model.getSelectionModel().isSelected(this);
}
@Override @Override
public ViewModel getViewModel() { public ViewModel getViewModel() {
if (this.viewModel == null) { if (this.viewModel == null) {

View File

@ -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<Relation> selectedRelations;
private final Set<Attribute> 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<Relation> getSelectedRelations() {
return new ArrayList<>(this.selectedRelations);
}
public List<Attribute> getSelectedAttributes() {
return new ArrayList<>(this.selectedAttributes);
}
public void select(Relation relation) {
this.selectedRelations.add(relation);
}
public void selectAll(Collection<Relation> 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();
}
}

View File

@ -2,7 +2,9 @@ package nl.andrewlalis.erme.view;
import nl.andrewlalis.erme.control.actions.edits.AddAttributeAction; import nl.andrewlalis.erme.control.actions.edits.AddAttributeAction;
import nl.andrewlalis.erme.control.actions.edits.AddRelationAction; 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.control.actions.edits.RemoveRelationAction;
import nl.andrewlalis.erme.model.Attribute;
import nl.andrewlalis.erme.model.Relation; import nl.andrewlalis.erme.model.Relation;
import javax.swing.*; import javax.swing.*;
@ -10,19 +12,19 @@ import java.util.List;
public class DiagramPopupMenu extends JPopupMenu { public class DiagramPopupMenu extends JPopupMenu {
public DiagramPopupMenu(DiagramPanel diagramPanel) { public DiagramPopupMenu(DiagramPanel diagramPanel) {
List<Relation> selectedRelations = diagramPanel.getModel().getSelectedRelations(); List<Relation> selectedRelations = diagramPanel.getModel().getSelectionModel().getSelectedRelations();
if (selectedRelations.size() == 0) { List<Attribute> selectedAttributes = diagramPanel.getModel().getSelectionModel().getSelectedAttributes();
if (selectedRelations.isEmpty() && selectedAttributes.isEmpty()) {
this.add(new AddRelationAction(diagramPanel)); this.add(new AddRelationAction(diagramPanel));
} }
if (selectedRelations.size() > 0) { if (selectedRelations.size() > 0 && selectedAttributes.isEmpty()) {
this.add(new RemoveRelationAction(diagramPanel)); this.add(new RemoveRelationAction(diagramPanel));
} }
if (selectedRelations.size() == 1) { if (selectedRelations.size() == 1 && selectedAttributes.isEmpty()) {
Relation relation = selectedRelations.get(0);
this.add(new AddAttributeAction(diagramPanel)); this.add(new AddAttributeAction(diagramPanel));
if (!relation.getAttributes().isEmpty()) { }
this.add(new RemoveRelationAction(diagramPanel)); if (!selectedAttributes.isEmpty() && selectedRelations.isEmpty()) {
} this.add(new RemoveAttributeAction(diagramPanel));
} }
} }
} }

View File

@ -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<AttributeType> 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();
}
}

View File

@ -3,6 +3,7 @@ package nl.andrewlalis.erme.view;
import nl.andrewlalis.erme.control.actions.*; import nl.andrewlalis.erme.control.actions.*;
import nl.andrewlalis.erme.control.actions.edits.AddAttributeAction; import nl.andrewlalis.erme.control.actions.edits.AddAttributeAction;
import nl.andrewlalis.erme.control.actions.edits.AddRelationAction; 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.control.actions.edits.RemoveRelationAction;
import javax.swing.*; import javax.swing.*;
@ -35,7 +36,7 @@ public class EditorMenuBar extends JMenuBar {
menu.add(new AddRelationAction(diagramPanel)); menu.add(new AddRelationAction(diagramPanel));
menu.add(new RemoveRelationAction(diagramPanel)); menu.add(new RemoveRelationAction(diagramPanel));
menu.add(new AddAttributeAction(diagramPanel)); menu.add(new AddAttributeAction(diagramPanel));
menu.add(new RemoveRelationAction(diagramPanel)); menu.add(new RemoveAttributeAction(diagramPanel));
menu.add(new AutoPositionAction(diagramPanel)); menu.add(new AutoPositionAction(diagramPanel));
return menu; return menu;
} }

View File

@ -2,7 +2,6 @@ package nl.andrewlalis.erme.view.view_models;
import nl.andrewlalis.erme.model.Attribute; import nl.andrewlalis.erme.model.Attribute;
import nl.andrewlalis.erme.model.AttributeType; import nl.andrewlalis.erme.model.AttributeType;
import nl.andrewlalis.erme.model.ForeignKeyAttribute;
import java.awt.*; import java.awt.*;
import java.awt.font.TextAttribute; 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_X = 5;
public static final int PADDING_Y = 5; public static final int PADDING_Y = 5;
public static final Color BACKGROUND_COLOR = new Color(192, 192, 192, 127); 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 Color FONT_COLOR = Color.BLACK;
public static final float FK_FONT_SIZE = 11.0f; public static final float FK_FONT_SIZE = 11.0f;
private static final float LOLCAT_SAT = 0.75f; private static final float LOLCAT_SAT = 0.75f;
@ -35,16 +35,16 @@ public class AttributeViewModel implements ViewModel {
g.setColor(FONT_COLOR); g.setColor(FONT_COLOR);
g.drawRect(r.x, r.y, r.width, r.height); g.drawRect(r.x, r.y, r.width, r.height);
g.drawString(as.getIterator(), r.x + PADDING_X, r.y + (r.height - PADDING_Y)); g.drawString(as.getIterator(), r.x + PADDING_X, r.y + (r.height - PADDING_Y));
if (this.attribute instanceof ForeignKeyAttribute) { if (this.attribute.hasReference() && !this.attribute.getRelation().getModel().isReferenceVisualizationEnabled()) {
ForeignKeyAttribute fkAttribute = (ForeignKeyAttribute) this.attribute;
Font originalFont = g.getFont(); Font originalFont = g.getFont();
g.setFont(g.getFont().deriveFont(Font.ITALIC, FK_FONT_SIZE)); 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); g.setFont(originalFont);
} }
} }
private Color getBackgroundColor(int x, int y, Graphics2D g) { private Color getBackgroundColor(int x, int y, Graphics2D g) {
if (attribute.isSelected()) return SELECTED_COLOR;
if (!attribute.getRelation().getModel().isLolcatEnabled()) return BACKGROUND_COLOR; if (!attribute.getRelation().getModel().isLolcatEnabled()) return BACKGROUND_COLOR;
Point offset = g.getClipBounds().getLocation(); Point offset = g.getClipBounds().getLocation();
g.translate(offset.x, offset.y); 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); Rectangle2D nameRect = g.getFontMetrics().getStringBounds(as.getIterator(), 0, this.attribute.getName().length(), g);
int width = (int) nameRect.getWidth() + (2 * PADDING_X); int width = (int) nameRect.getWidth() + (2 * PADDING_X);
int height = (int) nameRect.getHeight() + (2 * PADDING_Y); int height = (int) nameRect.getHeight() + (2 * PADDING_Y);
if (this.attribute instanceof ForeignKeyAttribute) { if (this.attribute.hasReference()) {
ForeignKeyAttribute fkAttribute = (ForeignKeyAttribute) this.attribute;
Font originalFont = g.getFont(); Font originalFont = g.getFont();
g.setFont(g.getFont().deriveFont(Font.ITALIC, FK_FONT_SIZE)); 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); g.setFont(originalFont);
width = Math.max(width, referenceNameBounds.width + (2 * PADDING_X)); width = Math.max(width, referenceNameBounds.width + (2 * PADDING_X));
} }
@ -103,4 +102,9 @@ public class AttributeViewModel implements ViewModel {
} }
return as; return as;
} }
private String getFullReferenceName() {
if (!this.attribute.hasReference()) return "";
return this.attribute.getReference().getRelation().getName() + "." + this.attribute.getReference().getName();
}
} }

View File

@ -1,7 +1,6 @@
package nl.andrewlalis.erme.view.view_models; package nl.andrewlalis.erme.view.view_models;
import nl.andrewlalis.erme.model.Attribute; import nl.andrewlalis.erme.model.Attribute;
import nl.andrewlalis.erme.model.ForeignKeyAttribute;
import nl.andrewlalis.erme.model.MappingModel; import nl.andrewlalis.erme.model.MappingModel;
import nl.andrewlalis.erme.model.Relation; import nl.andrewlalis.erme.model.Relation;
@ -32,13 +31,12 @@ public class MappingModelViewModel implements ViewModel {
g2.setStroke(dashedStroke); g2.setStroke(dashedStroke);
for (Relation r : this.model.getRelations()) { for (Relation r : this.model.getRelations()) {
for (Attribute a : r.getAttributes()) { for (Attribute a : r.getAttributes()) {
if (a instanceof ForeignKeyAttribute) { if (a.hasReference()) {
ForeignKeyAttribute fk = (ForeignKeyAttribute) a;
// Generate a random HSB color for the line, seeded using the referenced attribute's hash code. // 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)); g2.setColor(Color.getHSBColor(random.nextFloat(), 1.0f, 0.8f));
Rectangle sourceBounds = fk.getViewModel().getBounds(g); Rectangle sourceBounds = a.getViewModel().getBounds(g);
Rectangle targetBounds = fk.getReference().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 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); 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); g2.drawLine(sourcePoint.x, sourcePoint.y, targetPoint.x, targetPoint.y);