Added better user interface controls and panning.
This commit is contained in:
parent
471db2f60a
commit
d5546a5320
2
pom.xml
2
pom.xml
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
<groupId>nl.andrewlalis</groupId>
|
<groupId>nl.andrewlalis</groupId>
|
||||||
<artifactId>EntityRelationMappingEditor</artifactId>
|
<artifactId>EntityRelationMappingEditor</artifactId>
|
||||||
<version>1.1.0</version>
|
<version>1.2.0</version>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import com.formdev.flatlaf.FlatLightLaf;
|
||||||
import nl.andrewlalis.erme.view.EditorFrame;
|
import nl.andrewlalis.erme.view.EditorFrame;
|
||||||
|
|
||||||
public class EntityRelationMappingEditor {
|
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) {
|
public static void main(String[] args) {
|
||||||
if (!FlatLightLaf.install()) {
|
if (!FlatLightLaf.install()) {
|
||||||
|
|
|
@ -84,23 +84,13 @@ public class ExportToImageAction extends AbstractAction {
|
||||||
private BufferedImage renderModel() {
|
private BufferedImage renderModel() {
|
||||||
BufferedImage bufferedImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
|
BufferedImage bufferedImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
|
||||||
Graphics2D g2d = bufferedImage.createGraphics();
|
Graphics2D g2d = bufferedImage.createGraphics();
|
||||||
int minX = Integer.MAX_VALUE;
|
final Rectangle bounds = this.model.getViewModel().getBounds(g2d);
|
||||||
int minY = Integer.MAX_VALUE;
|
BufferedImage outputImage = new BufferedImage(bounds.width, bounds.height + 20, BufferedImage.TYPE_INT_RGB);
|
||||||
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);
|
|
||||||
g2d = outputImage.createGraphics();
|
g2d = outputImage.createGraphics();
|
||||||
g2d.setColor(Color.WHITE);
|
g2d.setColor(Color.WHITE);
|
||||||
g2d.fillRect(outputImage.getMinX(), outputImage.getMinY(), outputImage.getWidth(), outputImage.getHeight());
|
g2d.fillRect(outputImage.getMinX(), outputImage.getMinY(), outputImage.getWidth(), outputImage.getHeight());
|
||||||
AffineTransform originalTransform = g2d.getTransform();
|
AffineTransform originalTransform = g2d.getTransform();
|
||||||
g2d.setTransform(AffineTransform.getTranslateInstance(-minX, -minY));
|
g2d.setTransform(AffineTransform.getTranslateInstance(-bounds.x, -bounds.y));
|
||||||
|
|
||||||
List<Relation> selectedRelations = this.model.getSelectedRelations();
|
List<Relation> selectedRelations = this.model.getSelectedRelations();
|
||||||
this.model.getSelectedRelations().forEach(r -> r.setSelected(false));
|
this.model.getSelectedRelations().forEach(r -> r.setSelected(false));
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,6 @@ import nl.andrewlalis.erme.view.DiagramPopupMenu;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.InputEvent;
|
|
||||||
import java.awt.event.MouseAdapter;
|
import java.awt.event.MouseAdapter;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
|
|
||||||
|
@ -31,22 +30,29 @@ public class DiagramMouseListener extends MouseAdapter {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void mousePressed(MouseEvent e) {
|
public void mousePressed(MouseEvent e) {
|
||||||
DiagramPanel panel = (DiagramPanel) e.getSource();
|
final DiagramPanel panel = (DiagramPanel) e.getSource();
|
||||||
final Graphics2D g2d = panel.getGraphics2D();
|
final Graphics2D g = panel.getGraphics2D();
|
||||||
this.mouseDragStart = e.getPoint();
|
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));
|
this.model.getRelations().forEach(r -> r.setSelected(false));
|
||||||
}
|
}
|
||||||
for (Relation r : this.model.getRelations()) {
|
|
||||||
if (r.getViewModel().getBounds(g2d).contains(e.getX(), e.getY())) {
|
if (!isShiftDown) {// If the user clicked or CTRL+clicked, try and select the relation they clicked on.
|
||||||
r.setSelected(!r.isSelected());
|
for (Relation r : this.model.getRelations()) {
|
||||||
break;
|
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) {
|
if (e.getButton() == MouseEvent.BUTTON3) {
|
||||||
DiagramPopupMenu popupMenu = new DiagramPopupMenu(this.model);
|
DiagramPopupMenu popupMenu = new DiagramPopupMenu(this.model);
|
||||||
popupMenu.show(panel, e.getX(), e.getY());
|
popupMenu.show(panel, e.getX(), e.getY());
|
||||||
|
@ -62,15 +68,24 @@ public class DiagramMouseListener extends MouseAdapter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseDragged(MouseEvent e) {
|
public void mouseDragged(MouseEvent e) {
|
||||||
int dx = this.mouseDragStart.x - e.getX();
|
final int dx = this.mouseDragStart.x - e.getX();
|
||||||
int dy = this.mouseDragStart.y - e.getY();
|
final int dy = this.mouseDragStart.y - e.getY();
|
||||||
|
final boolean isShiftDown = (e.getModifiers() & ActionEvent.SHIFT_MASK) == ActionEvent.SHIFT_MASK;
|
||||||
boolean changed = false;
|
boolean changed = false;
|
||||||
for (Relation r : this.model.getRelations()) {
|
|
||||||
if (r.isSelected()) {
|
if (isShiftDown) {
|
||||||
r.setPosition(new Point(r.getPosition().x - dx, r.getPosition().y - dy));
|
final DiagramPanel panel = (DiagramPanel) e.getSource();
|
||||||
changed = true;
|
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) {
|
if (changed) {
|
||||||
this.model.fireChangedEvent();
|
this.model.fireChangedEvent();
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,4 +59,8 @@ public class Attribute implements Serializable {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return this.getName();
|
return this.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Attribute copy(Relation newRelation) {
|
||||||
|
return new Attribute(newRelation, this.getType(), this.getName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,4 +31,9 @@ public class ForeignKeyAttribute extends Attribute {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return super.toString() + "->" + this.getFullReferenceName();
|
return super.toString() + "->" + this.getFullReferenceName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ForeignKeyAttribute copy(Relation newRelation) {
|
||||||
|
return new ForeignKeyAttribute(newRelation, this.getType(), this.getName(), this.getReference());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package nl.andrewlalis.erme.model;
|
package nl.andrewlalis.erme.model;
|
||||||
|
|
||||||
import lombok.Getter;
|
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.io.Serializable;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -13,7 +16,7 @@ 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,
|
||||||
* including each mapped table and the links between them.
|
* including each mapped table and the links between them.
|
||||||
*/
|
*/
|
||||||
public class MappingModel implements Serializable {
|
public class MappingModel implements Serializable, Viewable {
|
||||||
@Getter
|
@Getter
|
||||||
private final Set<Relation> relations;
|
private final Set<Relation> relations;
|
||||||
|
|
||||||
|
@ -79,6 +82,23 @@ public class MappingModel implements Serializable {
|
||||||
this.changeListeners.forEach(ModelChangeListener::onModelChanged);
|
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
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
@ -91,4 +111,15 @@ public class MappingModel implements Serializable {
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(this.getRelations());
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package nl.andrewlalis.erme.model;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import nl.andrewlalis.erme.view.view_models.RelationViewModel;
|
import nl.andrewlalis.erme.view.view_models.RelationViewModel;
|
||||||
|
import nl.andrewlalis.erme.view.view_models.ViewModel;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
@ -14,7 +15,7 @@ import java.util.stream.Collectors;
|
||||||
* Represents a single "relation" or table in the diagram.
|
* Represents a single "relation" or table in the diagram.
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
public class Relation implements Serializable {
|
public class Relation implements Serializable, Viewable {
|
||||||
private final MappingModel model;
|
private final MappingModel model;
|
||||||
private Point position;
|
private Point position;
|
||||||
private String name;
|
private String name;
|
||||||
|
@ -59,7 +60,8 @@ public class Relation implements Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public RelationViewModel getViewModel() {
|
@Override
|
||||||
|
public ViewModel getViewModel() {
|
||||||
if (this.viewModel == null) {
|
if (this.viewModel == null) {
|
||||||
this.viewModel = new RelationViewModel(this);
|
this.viewModel = new RelationViewModel(this);
|
||||||
}
|
}
|
||||||
|
@ -89,4 +91,10 @@ public class Relation implements Serializable {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return this.getName();
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package nl.andrewlalis.erme.model;
|
||||||
|
|
||||||
|
import nl.andrewlalis.erme.view.view_models.ViewModel;
|
||||||
|
|
||||||
|
public interface Viewable {
|
||||||
|
ViewModel getViewModel();
|
||||||
|
}
|
|
@ -1,10 +1,7 @@
|
||||||
package nl.andrewlalis.erme.view;
|
package nl.andrewlalis.erme.view;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import nl.andrewlalis.erme.control.actions.ExportToImageAction;
|
import nl.andrewlalis.erme.control.actions.*;
|
||||||
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.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.RemoveAttributeAction;
|
||||||
|
@ -12,10 +9,11 @@ import nl.andrewlalis.erme.control.actions.edits.RemoveRelationAction;
|
||||||
import nl.andrewlalis.erme.control.diagram.DiagramMouseListener;
|
import nl.andrewlalis.erme.control.diagram.DiagramMouseListener;
|
||||||
import nl.andrewlalis.erme.model.MappingModel;
|
import nl.andrewlalis.erme.model.MappingModel;
|
||||||
import nl.andrewlalis.erme.model.ModelChangeListener;
|
import nl.andrewlalis.erme.model.ModelChangeListener;
|
||||||
import nl.andrewlalis.erme.view.view_models.MappingModelViewModel;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
import java.awt.event.MouseListener;
|
import java.awt.event.MouseListener;
|
||||||
import java.awt.event.MouseMotionListener;
|
import java.awt.event.MouseMotionListener;
|
||||||
|
|
||||||
|
@ -26,8 +24,30 @@ public class DiagramPanel extends JPanel implements ModelChangeListener {
|
||||||
@Getter
|
@Getter
|
||||||
private MappingModel model;
|
private MappingModel model;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final Point panningTranslation;
|
||||||
|
|
||||||
public DiagramPanel(MappingModel model) {
|
public DiagramPanel(MappingModel model) {
|
||||||
super(true);
|
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);
|
this.setModel(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,13 +64,38 @@ public class DiagramPanel extends JPanel implements ModelChangeListener {
|
||||||
this.addMouseListener(listener);
|
this.addMouseListener(listener);
|
||||||
this.addMouseMotionListener(listener);
|
this.addMouseMotionListener(listener);
|
||||||
this.updateActionModels();
|
this.updateActionModels();
|
||||||
|
this.centerModel();
|
||||||
this.repaint();
|
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
|
@Override
|
||||||
protected void paintComponent(Graphics g) {
|
protected void paintComponent(Graphics g) {
|
||||||
super.paintComponent(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) {
|
public Graphics2D getGraphics2D(Graphics g) {
|
||||||
|
@ -83,5 +128,6 @@ public class DiagramPanel extends JPanel implements ModelChangeListener {
|
||||||
RemoveRelationAction.getInstance().setModel(this.model);
|
RemoveRelationAction.getInstance().setModel(this.model);
|
||||||
AddAttributeAction.getInstance().setModel(this.model);
|
AddAttributeAction.getInstance().setModel(this.model);
|
||||||
RemoveAttributeAction.getInstance().setModel(this.model);
|
RemoveAttributeAction.getInstance().setModel(this.model);
|
||||||
|
LoadSampleModelAction.getInstance().setDiagramPanel(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ public class EditorFrame extends JFrame {
|
||||||
this.setMinimumSize(new Dimension(400, 400));
|
this.setMinimumSize(new Dimension(400, 400));
|
||||||
this.setPreferredSize(new Dimension(800, 800));
|
this.setPreferredSize(new Dimension(800, 800));
|
||||||
this.pack();
|
this.pack();
|
||||||
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
||||||
this.setLocationRelativeTo(null);
|
this.setLocationRelativeTo(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ public class EditorMenuBar extends JMenuBar {
|
||||||
private JMenu buildHelpMenu() {
|
private JMenu buildHelpMenu() {
|
||||||
JMenu menu = new JMenu("Help");
|
JMenu menu = new JMenu("Help");
|
||||||
menu.add(InstructionsAction.getInstance());
|
menu.add(InstructionsAction.getInstance());
|
||||||
|
menu.add(LoadSampleModelAction.getInstance());
|
||||||
menu.add(AboutAction.getInstance());
|
menu.add(AboutAction.getInstance());
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,9 @@ import java.awt.font.TextAttribute;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.text.AttributedString;
|
import java.text.AttributedString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View model for rendering a single attribute of a relation.
|
||||||
|
*/
|
||||||
public class AttributeViewModel implements ViewModel {
|
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;
|
||||||
|
@ -25,7 +28,7 @@ public class AttributeViewModel implements ViewModel {
|
||||||
@Override
|
@Override
|
||||||
public void draw(Graphics2D g) {
|
public void draw(Graphics2D g) {
|
||||||
AttributedString as = this.getAttributedString(g);
|
AttributedString as = this.getAttributedString(g);
|
||||||
Rectangle r = this.getBoxBounds(g, as);
|
Rectangle r = this.getBounds(g, as);
|
||||||
g.setColor(BACKGROUND_COLOR);
|
g.setColor(BACKGROUND_COLOR);
|
||||||
g.fillRect(r.x, r.y, r.width, r.height);
|
g.fillRect(r.x, r.y, r.width, r.height);
|
||||||
g.setColor(FONT_COLOR);
|
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 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;
|
int i = 0;
|
||||||
while (!this.attribute.getRelation().getAttributes().get(i).equals(this.attribute)) {
|
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++;
|
i++;
|
||||||
}
|
}
|
||||||
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);
|
||||||
|
@ -62,10 +71,6 @@ public class AttributeViewModel implements ViewModel {
|
||||||
return new Rectangle(x, y, width, height);
|
return new Rectangle(x, y, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rectangle getBoxBounds(Graphics2D g) {
|
|
||||||
return this.getBoxBounds(g, this.getAttributedString(g));
|
|
||||||
}
|
|
||||||
|
|
||||||
private AttributedString getAttributedString(Graphics2D g) {
|
private AttributedString getAttributedString(Graphics2D g) {
|
||||||
AttributedString as = new AttributedString(this.attribute.getName());
|
AttributedString as = new AttributedString(this.attribute.getName());
|
||||||
as.addAttribute(TextAttribute.FONT, g.getFont());
|
as.addAttribute(TextAttribute.FONT, g.getFont());
|
||||||
|
|
|
@ -5,6 +5,9 @@ import nl.andrewlalis.erme.model.Relation;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View model for rendering an entire {@code MappingModel} object.
|
||||||
|
*/
|
||||||
public class MappingModelViewModel implements ViewModel {
|
public class MappingModelViewModel implements ViewModel {
|
||||||
private final MappingModel model;
|
private final MappingModel model;
|
||||||
|
|
||||||
|
@ -18,4 +21,20 @@ public class MappingModelViewModel implements ViewModel {
|
||||||
r.getViewModel().draw(g);
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,28 @@ import java.awt.font.TextAttribute;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.text.AttributedString;
|
import java.text.AttributedString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View model which handles rendering a single relation.
|
||||||
|
*/
|
||||||
public class RelationViewModel implements ViewModel {
|
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;
|
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;
|
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 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;
|
private final Relation relation;
|
||||||
|
|
||||||
public RelationViewModel(Relation relation) {
|
public RelationViewModel(Relation relation) {
|
||||||
|
@ -23,17 +40,23 @@ public class RelationViewModel implements ViewModel {
|
||||||
public void draw(Graphics2D g) {
|
public void draw(Graphics2D g) {
|
||||||
AttributedString as = this.getAttributedString(g);
|
AttributedString as = this.getAttributedString(g);
|
||||||
Rectangle bounds = this.getBounds(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);
|
g.drawString(as.getIterator(), bounds.x + PADDING_X, bounds.y + this.getNameBounds(g).height - PADDING_Y);
|
||||||
for (Attribute a : this.relation.getAttributes()) {
|
for (Attribute a : this.relation.getAttributes()) {
|
||||||
a.getViewModel().draw(g);
|
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) {
|
public Rectangle getBounds(Graphics2D g) {
|
||||||
Rectangle rect = new Rectangle();
|
Rectangle rect = new Rectangle();
|
||||||
rect.x = this.relation.getPosition().x;
|
rect.x = this.relation.getPosition().x;
|
||||||
|
@ -41,7 +64,7 @@ public class RelationViewModel implements ViewModel {
|
||||||
int totalAttributeWidth = 0;
|
int totalAttributeWidth = 0;
|
||||||
int maxAttributeHeight = 0;
|
int maxAttributeHeight = 0;
|
||||||
for (Attribute a : this.relation.getAttributes()) {
|
for (Attribute a : this.relation.getAttributes()) {
|
||||||
Rectangle attributeBounds = a.getViewModel().getBoxBounds(g);
|
Rectangle attributeBounds = a.getViewModel().getBounds(g);
|
||||||
totalAttributeWidth += attributeBounds.width;
|
totalAttributeWidth += attributeBounds.width;
|
||||||
maxAttributeHeight = Math.max(maxAttributeHeight, attributeBounds.height);
|
maxAttributeHeight = Math.max(maxAttributeHeight, attributeBounds.height);
|
||||||
}
|
}
|
||||||
|
@ -51,12 +74,24 @@ public class RelationViewModel implements ViewModel {
|
||||||
return rect;
|
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) {
|
public Rectangle getNameBounds(Graphics2D g) {
|
||||||
AttributedString as = this.getAttributedString(g);
|
AttributedString as = this.getAttributedString(g);
|
||||||
Rectangle2D nameBounds = g.getFontMetrics().getStringBounds(as.getIterator(), 0, this.relation.getName().length(), g);
|
Rectangle2D nameBounds = g.getFontMetrics().getStringBounds(as.getIterator(), 0, this.relation.getName().length(), g);
|
||||||
return nameBounds.getBounds();
|
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) {
|
private AttributedString getAttributedString(Graphics2D g) {
|
||||||
AttributedString as = new AttributedString(this.relation.getName());
|
AttributedString as = new AttributedString(this.relation.getName());
|
||||||
as.addAttribute(TextAttribute.FONT, g.getFont());
|
as.addAttribute(TextAttribute.FONT, g.getFont());
|
||||||
|
|
|
@ -4,4 +4,6 @@ import java.awt.*;
|
||||||
|
|
||||||
public interface ViewModel {
|
public interface ViewModel {
|
||||||
void draw(Graphics2D g);
|
void draw(Graphics2D g);
|
||||||
|
|
||||||
|
Rectangle getBounds(Graphics2D g);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
The <em>Edit</em> menu contains options for making changes to the current model, such as adding or removing relations and attributes, or undoing/redoing actions.
|
The <em>Edit</em> menu contains options for making changes to the current model, such as adding or removing relations and attributes, or undoing/redoing actions.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
The <em>Help</em> menu contains some items with additional information about the application, like this help page and a simple <em>About</em> popup with version information.
|
The <em>Help</em> menu contains some items with additional information about the application, like this help page and a simple <em>About</em> popup with version information. There's also a <em>Load Sample Model</em> option, which will load a very basic sample model into the application that you are free to mess around with.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
|
@ -47,6 +47,12 @@
|
||||||
<li>
|
<li>
|
||||||
Right-click on different areas to access a context menu with some helpful actions.
|
Right-click on different areas to access a context menu with some helpful actions.
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
Hold <em>Shift</em> while dragging the mouse to pan across the model. To reset panning, press <em>Shift + Space</em>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Press <em>Control + Space</em> to move the view so that the diagram is centered.
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
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.
|
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.
|
||||||
</li>
|
</li>
|
||||||
|
|
Loading…
Reference in New Issue