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.