diff --git a/pom.xml b/pom.xml
index 455ffaa..779af24 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
nl.andrewlalis
EntityRelationMappingEditor
- 1.1.0
+ 1.2.0
diff --git a/src/main/java/nl/andrewlalis/erme/EntityRelationMappingEditor.java b/src/main/java/nl/andrewlalis/erme/EntityRelationMappingEditor.java
index cdea011..5180efb 100644
--- a/src/main/java/nl/andrewlalis/erme/EntityRelationMappingEditor.java
+++ b/src/main/java/nl/andrewlalis/erme/EntityRelationMappingEditor.java
@@ -4,7 +4,7 @@ import com.formdev.flatlaf.FlatLightLaf;
import nl.andrewlalis.erme.view.EditorFrame;
public class EntityRelationMappingEditor {
- public static final String VERSION = "1.1.0";
+ public static final String VERSION = "1.2.0";
public static void main(String[] args) {
if (!FlatLightLaf.install()) {
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 0a24b23..a9774be 100644
--- a/src/main/java/nl/andrewlalis/erme/control/actions/ExportToImageAction.java
+++ b/src/main/java/nl/andrewlalis/erme/control/actions/ExportToImageAction.java
@@ -84,23 +84,13 @@ public class ExportToImageAction extends AbstractAction {
private BufferedImage renderModel() {
BufferedImage bufferedImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = bufferedImage.createGraphics();
- int minX = Integer.MAX_VALUE;
- int minY = Integer.MAX_VALUE;
- int maxX = Integer.MIN_VALUE;
- int maxY = Integer.MIN_VALUE;
- for (Relation r : model.getRelations()) {
- Rectangle bounds = r.getViewModel().getBounds(g2d);
- minX = Math.min(minX, bounds.x);
- minY = Math.min(minY, bounds.y);
- maxX = Math.max(maxX, bounds.x + bounds.width);
- maxY = Math.max(maxY, bounds.y + bounds.height);
- }
- BufferedImage outputImage = new BufferedImage((maxX - minX), (maxY - minY) + 20, BufferedImage.TYPE_INT_RGB);
+ final Rectangle bounds = this.model.getViewModel().getBounds(g2d);
+ BufferedImage outputImage = new BufferedImage(bounds.width, bounds.height + 20, BufferedImage.TYPE_INT_RGB);
g2d = outputImage.createGraphics();
g2d.setColor(Color.WHITE);
g2d.fillRect(outputImage.getMinX(), outputImage.getMinY(), outputImage.getWidth(), outputImage.getHeight());
AffineTransform originalTransform = g2d.getTransform();
- g2d.setTransform(AffineTransform.getTranslateInstance(-minX, -minY));
+ g2d.setTransform(AffineTransform.getTranslateInstance(-bounds.x, -bounds.y));
List selectedRelations = this.model.getSelectedRelations();
this.model.getSelectedRelations().forEach(r -> r.setSelected(false));
diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/LoadSampleModelAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/LoadSampleModelAction.java
new file mode 100644
index 0000000..7c7b42e
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/erme/control/actions/LoadSampleModelAction.java
@@ -0,0 +1,43 @@
+package nl.andrewlalis.erme.control.actions;
+
+import lombok.Setter;
+import nl.andrewlalis.erme.model.*;
+import nl.andrewlalis.erme.view.DiagramPanel;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+
+public class LoadSampleModelAction extends AbstractAction {
+ private static LoadSampleModelAction instance;
+
+ public static LoadSampleModelAction getInstance() {
+ if (instance == null) {
+ instance = new LoadSampleModelAction();
+ }
+ return instance;
+ }
+
+ @Setter
+ private DiagramPanel diagramPanel;
+
+ public LoadSampleModelAction() {
+ super("Load Sample Model");
+ this.putValue(SHORT_DESCRIPTION, "Loads a sample ER-mapping model.");
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ MappingModel model = new MappingModel();
+ Relation r0 = new Relation(model, new Point(50, 20), "AirplaneType");
+ r0.addAttribute(new Attribute(r0, AttributeType.ID_KEY, "name"));
+ r0.addAttribute(new Attribute(r0, AttributeType.PLAIN, "manufacturer"));
+ model.addRelation(r0);
+ 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"));
+ model.addRelation(r1);
+ this.diagramPanel.setModel(model);
+ }
+}
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 113f072..5beaadb 100644
--- a/src/main/java/nl/andrewlalis/erme/control/diagram/DiagramMouseListener.java
+++ b/src/main/java/nl/andrewlalis/erme/control/diagram/DiagramMouseListener.java
@@ -7,7 +7,6 @@ import nl.andrewlalis.erme.view.DiagramPopupMenu;
import java.awt.*;
import java.awt.event.ActionEvent;
-import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
@@ -31,22 +30,29 @@ public class DiagramMouseListener extends MouseAdapter {
*/
@Override
public void mousePressed(MouseEvent e) {
- DiagramPanel panel = (DiagramPanel) e.getSource();
- final Graphics2D g2d = panel.getGraphics2D();
+ final DiagramPanel panel = (DiagramPanel) e.getSource();
+ final Graphics2D g = panel.getGraphics2D();
this.mouseDragStart = e.getPoint();
+ final int modelX = e.getX() - panel.getPanningTranslation().x;
+ final int modelY = e.getY() - panel.getPanningTranslation().y;
- boolean isCtrlDown = (e.getModifiers() & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK;
+ final boolean isCtrlDown = (e.getModifiers() & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK;
+ final boolean isShiftDown = (e.getModifiers() & ActionEvent.SHIFT_MASK) == ActionEvent.SHIFT_MASK;
- if (!isCtrlDown) {
+ if (!isShiftDown && !isCtrlDown) {// A simple click anywhere should reset selection.
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;
+
+ if (!isShiftDown) {// If the user clicked or CTRL+clicked, try and select the relation they clicked on.
+ for (Relation r : this.model.getRelations()) {
+ if (r.getViewModel().getBounds(g).contains(modelX, modelY)) {
+ r.setSelected(!r.isSelected());
+ break;
+ }
}
}
+ // If the user right-clicked, show a popup menu.
if (e.getButton() == MouseEvent.BUTTON3) {
DiagramPopupMenu popupMenu = new DiagramPopupMenu(this.model);
popupMenu.show(panel, e.getX(), e.getY());
@@ -62,15 +68,24 @@ public class DiagramMouseListener extends MouseAdapter {
@Override
public void mouseDragged(MouseEvent e) {
- int dx = this.mouseDragStart.x - e.getX();
- int dy = this.mouseDragStart.y - e.getY();
+ final int dx = this.mouseDragStart.x - e.getX();
+ final int dy = this.mouseDragStart.y - e.getY();
+ final boolean isShiftDown = (e.getModifiers() & ActionEvent.SHIFT_MASK) == ActionEvent.SHIFT_MASK;
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 (isShiftDown) {
+ final DiagramPanel panel = (DiagramPanel) e.getSource();
+ panel.translate(-dx, -dy);
+ panel.repaint();
+ } else {
+ 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();
}
diff --git a/src/main/java/nl/andrewlalis/erme/model/Attribute.java b/src/main/java/nl/andrewlalis/erme/model/Attribute.java
index a6ef1d3..4f9bcb0 100644
--- a/src/main/java/nl/andrewlalis/erme/model/Attribute.java
+++ b/src/main/java/nl/andrewlalis/erme/model/Attribute.java
@@ -59,4 +59,8 @@ public class Attribute implements Serializable {
public String toString() {
return this.getName();
}
+
+ public Attribute copy(Relation newRelation) {
+ return new Attribute(newRelation, this.getType(), this.getName());
+ }
}
diff --git a/src/main/java/nl/andrewlalis/erme/model/ForeignKeyAttribute.java b/src/main/java/nl/andrewlalis/erme/model/ForeignKeyAttribute.java
index 00c72b2..2b9b011 100644
--- a/src/main/java/nl/andrewlalis/erme/model/ForeignKeyAttribute.java
+++ b/src/main/java/nl/andrewlalis/erme/model/ForeignKeyAttribute.java
@@ -31,4 +31,9 @@ public class ForeignKeyAttribute extends Attribute {
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 e576346..328f9ad 100644
--- a/src/main/java/nl/andrewlalis/erme/model/MappingModel.java
+++ b/src/main/java/nl/andrewlalis/erme/model/MappingModel.java
@@ -1,7 +1,10 @@
package nl.andrewlalis.erme.model;
import lombok.Getter;
+import nl.andrewlalis.erme.view.view_models.MappingModelViewModel;
+import nl.andrewlalis.erme.view.view_models.ViewModel;
+import java.awt.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.List;
@@ -13,7 +16,7 @@ import java.util.stream.Collectors;
* This model contains all the information about a single mapping diagram,
* including each mapped table and the links between them.
*/
-public class MappingModel implements Serializable {
+public class MappingModel implements Serializable, Viewable {
@Getter
private final Set relations;
@@ -79,6 +82,23 @@ public class MappingModel implements Serializable {
this.changeListeners.forEach(ModelChangeListener::onModelChanged);
}
+ /**
+ * Updates the positions of all relations so that the bounding box for this
+ * model starts at 0, 0.
+ */
+ public final void normalizeRelationPositions() {
+ int minX = Integer.MAX_VALUE;
+ int minY = Integer.MAX_VALUE;
+ for (Relation r : this.getRelations()) {
+ minX = Math.min(minX, r.getPosition().x);
+ minY = Math.min(minY, r.getPosition().y);
+ }
+ for (Relation r : this.getRelations()) {
+ final Point current = r.getPosition();
+ r.setPosition(new Point(current.x - minX, current.y - minY));
+ }
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -91,4 +111,15 @@ public class MappingModel implements Serializable {
public int hashCode() {
return Objects.hash(this.getRelations());
}
+
+ @Override
+ public ViewModel getViewModel() {
+ return new MappingModelViewModel(this);
+ }
+
+ public MappingModel copy() {
+ MappingModel c = new MappingModel();
+ this.getRelations().forEach(r -> c.addRelation(r.copy(c)));
+ return c;
+ }
}
diff --git a/src/main/java/nl/andrewlalis/erme/model/Relation.java b/src/main/java/nl/andrewlalis/erme/model/Relation.java
index 4722c2e..05c0e66 100644
--- a/src/main/java/nl/andrewlalis/erme/model/Relation.java
+++ b/src/main/java/nl/andrewlalis/erme/model/Relation.java
@@ -2,6 +2,7 @@ package nl.andrewlalis.erme.model;
import lombok.Getter;
import nl.andrewlalis.erme.view.view_models.RelationViewModel;
+import nl.andrewlalis.erme.view.view_models.ViewModel;
import java.awt.*;
import java.io.Serializable;
@@ -14,7 +15,7 @@ import java.util.stream.Collectors;
* Represents a single "relation" or table in the diagram.
*/
@Getter
-public class Relation implements Serializable {
+public class Relation implements Serializable, Viewable {
private final MappingModel model;
private Point position;
private String name;
@@ -59,7 +60,8 @@ public class Relation implements Serializable {
}
}
- public RelationViewModel getViewModel() {
+ @Override
+ public ViewModel getViewModel() {
if (this.viewModel == null) {
this.viewModel = new RelationViewModel(this);
}
@@ -89,4 +91,10 @@ public class Relation implements Serializable {
public String toString() {
return this.getName();
}
+
+ public Relation copy(MappingModel newModel) {
+ Relation c = new Relation(newModel, new Point(this.getPosition()), this.getName());
+ this.getAttributes().forEach(a -> c.addAttribute(a.copy(c)));
+ return c;
+ }
}
diff --git a/src/main/java/nl/andrewlalis/erme/model/Viewable.java b/src/main/java/nl/andrewlalis/erme/model/Viewable.java
new file mode 100644
index 0000000..6f1f263
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/erme/model/Viewable.java
@@ -0,0 +1,7 @@
+package nl.andrewlalis.erme.model;
+
+import nl.andrewlalis.erme.view.view_models.ViewModel;
+
+public interface Viewable {
+ ViewModel getViewModel();
+}
diff --git a/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java b/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java
index 7d63ce4..b5f10e8 100644
--- a/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java
+++ b/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java
@@ -1,10 +1,7 @@
package nl.andrewlalis.erme.view;
import lombok.Getter;
-import nl.andrewlalis.erme.control.actions.ExportToImageAction;
-import nl.andrewlalis.erme.control.actions.LoadAction;
-import nl.andrewlalis.erme.control.actions.NewModelAction;
-import nl.andrewlalis.erme.control.actions.SaveAction;
+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;
@@ -12,10 +9,11 @@ import nl.andrewlalis.erme.control.actions.edits.RemoveRelationAction;
import nl.andrewlalis.erme.control.diagram.DiagramMouseListener;
import nl.andrewlalis.erme.model.MappingModel;
import nl.andrewlalis.erme.model.ModelChangeListener;
-import nl.andrewlalis.erme.view.view_models.MappingModelViewModel;
import javax.swing.*;
import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
@@ -26,8 +24,30 @@ public class DiagramPanel extends JPanel implements ModelChangeListener {
@Getter
private MappingModel model;
+ @Getter
+ private final Point panningTranslation;
+
public DiagramPanel(MappingModel model) {
super(true);
+ this.panningTranslation = new Point();
+ InputMap inputMap = this.getInputMap(WHEN_IN_FOCUSED_WINDOW);
+ inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, KeyEvent.SHIFT_DOWN_MASK), "PAN_RESET");
+ inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, KeyEvent.CTRL_DOWN_MASK), "CENTER_MODEL");
+ this.getActionMap().put("PAN_RESET", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ resetTranslation();
+ repaint();
+ }
+ });
+ this.getActionMap().put("CENTER_MODEL", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ model.normalizeRelationPositions();
+ centerModel();
+ repaint();
+ }
+ });
this.setModel(model);
}
@@ -44,13 +64,38 @@ public class DiagramPanel extends JPanel implements ModelChangeListener {
this.addMouseListener(listener);
this.addMouseMotionListener(listener);
this.updateActionModels();
+ this.centerModel();
this.repaint();
}
+ public void translate(int dx, int dy) {
+ this.panningTranslation.x += dx;
+ this.panningTranslation.y += dy;
+ }
+
+ public void resetTranslation() {
+ this.panningTranslation.x = 0;
+ this.panningTranslation.y = 0;
+ }
+
+ public void centerModel() {
+ if (this.getGraphics() == null) {
+ return;
+ }
+ final Rectangle modelBounds = this.getModel().getViewModel().getBounds(this.getGraphics2D());
+ final int modelCenterX = modelBounds.x + modelBounds.width / 2;
+ final int modelCenterY = modelBounds.y + modelBounds.height / 2;
+ final int panelCenterX = this.getWidth() / 2;
+ final int panelCenterY = this.getHeight() / 2;
+ this.resetTranslation();
+ this.translate(panelCenterX - modelCenterX, panelCenterY - modelCenterY);
+ }
+
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
- new MappingModelViewModel(this.model).draw(this.getGraphics2D(g));
+ g.translate(this.panningTranslation.x, this.panningTranslation.y);
+ this.model.getViewModel().draw(this.getGraphics2D(g));
}
public Graphics2D getGraphics2D(Graphics g) {
@@ -83,5 +128,6 @@ public class DiagramPanel extends JPanel implements ModelChangeListener {
RemoveRelationAction.getInstance().setModel(this.model);
AddAttributeAction.getInstance().setModel(this.model);
RemoveAttributeAction.getInstance().setModel(this.model);
+ LoadSampleModelAction.getInstance().setDiagramPanel(this);
}
}
diff --git a/src/main/java/nl/andrewlalis/erme/view/EditorFrame.java b/src/main/java/nl/andrewlalis/erme/view/EditorFrame.java
index c4d06bb..362cfb8 100644
--- a/src/main/java/nl/andrewlalis/erme/view/EditorFrame.java
+++ b/src/main/java/nl/andrewlalis/erme/view/EditorFrame.java
@@ -16,7 +16,7 @@ public class EditorFrame extends JFrame {
this.setMinimumSize(new Dimension(400, 400));
this.setPreferredSize(new Dimension(800, 800));
this.pack();
- this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+ this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
}
}
diff --git a/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java b/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java
index 60df1d6..9192bdd 100644
--- a/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java
+++ b/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java
@@ -45,6 +45,7 @@ public class EditorMenuBar extends JMenuBar {
private JMenu buildHelpMenu() {
JMenu menu = new JMenu("Help");
menu.add(InstructionsAction.getInstance());
+ menu.add(LoadSampleModelAction.getInstance());
menu.add(AboutAction.getInstance());
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 e58a692..6612554 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
@@ -9,6 +9,9 @@ import java.awt.font.TextAttribute;
import java.awt.geom.Rectangle2D;
import java.text.AttributedString;
+/**
+ * View model for rendering a single attribute of a relation.
+ */
public class AttributeViewModel implements ViewModel {
public static final int PADDING_X = 5;
public static final int PADDING_Y = 5;
@@ -25,7 +28,7 @@ public class AttributeViewModel implements ViewModel {
@Override
public void draw(Graphics2D g) {
AttributedString as = this.getAttributedString(g);
- Rectangle r = this.getBoxBounds(g, as);
+ Rectangle r = this.getBounds(g, as);
g.setColor(BACKGROUND_COLOR);
g.fillRect(r.x, r.y, r.width, r.height);
g.setColor(FONT_COLOR);
@@ -40,12 +43,18 @@ public class AttributeViewModel implements ViewModel {
}
}
- private Rectangle getBoxBounds(Graphics2D g, AttributedString as) {
+ @Override
+ public Rectangle getBounds(Graphics2D g) {
+ return this.getBounds(g, this.getAttributedString(g));
+ }
+
+ private Rectangle getBounds(Graphics2D g, AttributedString as) {
+ final RelationViewModel relationViewModel = (RelationViewModel) this.attribute.getRelation().getViewModel();
int x = this.attribute.getRelation().getPosition().x + RelationViewModel.PADDING_X;
- int y = this.attribute.getRelation().getPosition().y + this.attribute.getRelation().getViewModel().getNameBounds(g).height + RelationViewModel.ATTRIBUTE_SEPARATION;
+ int y = this.attribute.getRelation().getPosition().y + relationViewModel.getNameBounds(g).height + RelationViewModel.ATTRIBUTE_SEPARATION;
int i = 0;
while (!this.attribute.getRelation().getAttributes().get(i).equals(this.attribute)) {
- x += this.attribute.getRelation().getAttributes().get(i).getViewModel().getBoxBounds(g).width;
+ x += this.attribute.getRelation().getAttributes().get(i).getViewModel().getBounds(g).width;
i++;
}
Rectangle2D nameRect = g.getFontMetrics().getStringBounds(as.getIterator(), 0, this.attribute.getName().length(), g);
@@ -62,10 +71,6 @@ public class AttributeViewModel implements ViewModel {
return new Rectangle(x, y, width, height);
}
- public Rectangle getBoxBounds(Graphics2D g) {
- return this.getBoxBounds(g, this.getAttributedString(g));
- }
-
private AttributedString getAttributedString(Graphics2D g) {
AttributedString as = new AttributedString(this.attribute.getName());
as.addAttribute(TextAttribute.FONT, g.getFont());
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 a488402..686f468 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
@@ -5,6 +5,9 @@ import nl.andrewlalis.erme.model.Relation;
import java.awt.*;
+/**
+ * View model for rendering an entire {@code MappingModel} object.
+ */
public class MappingModelViewModel implements ViewModel {
private final MappingModel model;
@@ -18,4 +21,20 @@ public class MappingModelViewModel implements ViewModel {
r.getViewModel().draw(g);
}
}
+
+ @Override
+ public Rectangle getBounds(Graphics2D g) {
+ int minX = Integer.MAX_VALUE;
+ int minY = Integer.MAX_VALUE;
+ int maxX = Integer.MIN_VALUE;
+ int maxY = Integer.MIN_VALUE;
+ for (Relation r : model.getRelations()) {
+ Rectangle bounds = r.getViewModel().getBounds(g);
+ minX = Math.min(minX, bounds.x);
+ minY = Math.min(minY, bounds.y);
+ maxX = Math.max(maxX, bounds.x + bounds.width);
+ maxY = Math.max(maxY, bounds.y + bounds.height);
+ }
+ return new Rectangle(minX, minY, Math.abs(maxX - minX), Math.abs(maxY - minY));
+ }
}
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 21c02c4..34fcdb9 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
@@ -8,11 +8,28 @@ import java.awt.font.TextAttribute;
import java.awt.geom.Rectangle2D;
import java.text.AttributedString;
+/**
+ * View model which handles rendering a single relation.
+ */
public class RelationViewModel implements ViewModel {
+ /**
+ * Padding that should be added to the left and right of the true bounds.
+ */
public static final int PADDING_X = 5;
+
+ /**
+ * Padding that should be added to the top and bottom of the true bounds.
+ */
public static final int PADDING_Y = 5;
+
+ /**
+ * The space between the relation's name and any attributes.
+ */
public static final int ATTRIBUTE_SEPARATION = 10;
+ public static final Color SELECTED_COLOR = new Color(204, 224, 255);
+ public static final Color NAME_COLOR = Color.BLACK;
+
private final Relation relation;
public RelationViewModel(Relation relation) {
@@ -23,17 +40,23 @@ public class RelationViewModel implements ViewModel {
public void draw(Graphics2D g) {
AttributedString as = this.getAttributedString(g);
Rectangle bounds = this.getBounds(g);
- g.setColor(Color.BLACK);
+ if (this.relation.isSelected()) {
+ g.setColor(SELECTED_COLOR);
+ g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
+ }
+ g.setColor(NAME_COLOR);
g.drawString(as.getIterator(), bounds.x + PADDING_X, bounds.y + this.getNameBounds(g).height - PADDING_Y);
for (Attribute a : this.relation.getAttributes()) {
a.getViewModel().draw(g);
}
- if (this.relation.isSelected()) {
- g.setColor(Color.BLUE);
- g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
- }
}
+ /**
+ * Obtains the bounding box for the relation that'll be drawn. The bounding
+ * box contains the name of the relation and any attributes in it.
+ * @param g The graphics context.
+ * @return A rectangle describing the bounding box.
+ */
public Rectangle getBounds(Graphics2D g) {
Rectangle rect = new Rectangle();
rect.x = this.relation.getPosition().x;
@@ -41,7 +64,7 @@ public class RelationViewModel implements ViewModel {
int totalAttributeWidth = 0;
int maxAttributeHeight = 0;
for (Attribute a : this.relation.getAttributes()) {
- Rectangle attributeBounds = a.getViewModel().getBoxBounds(g);
+ Rectangle attributeBounds = a.getViewModel().getBounds(g);
totalAttributeWidth += attributeBounds.width;
maxAttributeHeight = Math.max(maxAttributeHeight, attributeBounds.height);
}
@@ -51,12 +74,24 @@ public class RelationViewModel implements ViewModel {
return rect;
}
+ /**
+ * Gets the bounding box around the relation's name, according to the font,
+ * font size, weight, etc.
+ * @param g The graphics context.
+ * @return A rectangle describing the name's bounding box.
+ */
public Rectangle getNameBounds(Graphics2D g) {
AttributedString as = this.getAttributedString(g);
Rectangle2D nameBounds = g.getFontMetrics().getStringBounds(as.getIterator(), 0, this.relation.getName().length(), g);
return nameBounds.getBounds();
}
+ /**
+ * Gets an instance of AttributedString that can be used for display and
+ * reference purposes.
+ * @param g The graphics context.
+ * @return The attributed string, with all necessary attributes set.
+ */
private AttributedString getAttributedString(Graphics2D g) {
AttributedString as = new AttributedString(this.relation.getName());
as.addAttribute(TextAttribute.FONT, g.getFont());
diff --git a/src/main/java/nl/andrewlalis/erme/view/view_models/ViewModel.java b/src/main/java/nl/andrewlalis/erme/view/view_models/ViewModel.java
index e3d8989..7dee8bd 100644
--- a/src/main/java/nl/andrewlalis/erme/view/view_models/ViewModel.java
+++ b/src/main/java/nl/andrewlalis/erme/view/view_models/ViewModel.java
@@ -4,4 +4,6 @@ import java.awt.*;
public interface ViewModel {
void draw(Graphics2D g);
+
+ Rectangle getBounds(Graphics2D g);
}
diff --git a/src/main/resources/html/instructions.html b/src/main/resources/html/instructions.html
index edb7a05..79c3cba 100644
--- a/src/main/resources/html/instructions.html
+++ b/src/main/resources/html/instructions.html
@@ -28,7 +28,7 @@
The Edit menu contains options for making changes to the current model, such as adding or removing relations and attributes, or undoing/redoing actions.
- The Help menu contains some items with additional information about the application, like this help page and a simple About popup with version information.
+ The Help menu contains some items with additional information about the application, like this help page and a simple About popup with version information. There's also a Load Sample Model option, which will load a very basic sample model into the application that you are free to mess around with.
@@ -47,6 +47,12 @@
Right-click on different areas to access a context menu with some helpful actions.
+
+ Hold Shift while dragging the mouse to pan across the model. To reset panning, press Shift + Space.
+
+
+ Press Control + Space to move the view so that the diagram is centered.
+
When exporting the model to an image, be sure to add the desired image file extension (.png, .jpg, .bmp, etc.), or the application will default to .png.